Compare commits
	
		
			1 Commits
		
	
	
		
			nightly-v2
			...
			pierremtb/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 34272b872d | 
							
								
								
									
										59
									
								
								.github/ci-cd-scripts/playwright-browser-chrome.sh
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,59 @@ | |||||||
|  | # bash strict mode | ||||||
|  | set -euo pipefail | ||||||
|  |  | ||||||
|  | if [[ ! -f "test-results/.last-run.json" ]]; then | ||||||
|  |     # if no last run artifact, than run plawright normally | ||||||
|  |     echo "run playwright normally" | ||||||
|  |     if [[ "$3" == ubuntu-latest* ]]; then | ||||||
|  |         yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true | ||||||
|  |     elif [[ "$3" == windows-latest* ]]; then | ||||||
|  |         yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true | ||||||
|  |     else | ||||||
|  |         echo "Do not run playwright. Unable to detect os runtime." | ||||||
|  |         exit 1 | ||||||
|  |     fi | ||||||
|  |     # # send to axiom | ||||||
|  |     node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | retry=1 | ||||||
|  | max_retrys=4 | ||||||
|  |  | ||||||
|  | # retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues | ||||||
|  | while [[ $retry -le $max_retrys ]]; do | ||||||
|  |     if [[ -f "test-results/.last-run.json" ]]; then | ||||||
|  |         failed_tests=$(jq '.failedTests | length' test-results/.last-run.json) | ||||||
|  |         if [[ $failed_tests -gt 0 ]]; then | ||||||
|  |             echo "retried=true" >>$GITHUB_OUTPUT | ||||||
|  |             echo "run playwright with last failed tests and retry $retry" | ||||||
|  |             if [[ "$3" == ubuntu-latest* ]]; then | ||||||
|  |                 yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true | ||||||
|  |             elif [[ "$3" == windows-latest* ]]; then | ||||||
|  |                 yarn test:playwright:browser:chrome:windows -- --last-failed || true | ||||||
|  |             else | ||||||
|  |                 echo "Do not run playwright. Unable to detect os runtime." | ||||||
|  |                 exit 1 | ||||||
|  |             fi | ||||||
|  |             # send to axiom | ||||||
|  |             node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 | ||||||
|  |             retry=$((retry + 1)) | ||||||
|  |         else | ||||||
|  |             echo "retried=false" >>$GITHUB_OUTPUT | ||||||
|  |             exit 0 | ||||||
|  |         fi | ||||||
|  |     else | ||||||
|  |         echo "retried=false" >>$GITHUB_OUTPUT | ||||||
|  |         exit 0 | ||||||
|  |     fi | ||||||
|  | done | ||||||
|  |  | ||||||
|  | echo "retried=false" >>$GITHUB_OUTPUT | ||||||
|  |  | ||||||
|  | if [[ -f "test-results/.last-run.json" ]]; then | ||||||
|  |     failed_tests=$(jq '.failedTests | length' test-results/.last-run.json) | ||||||
|  |     if [[ $failed_tests -gt 0 ]]; then | ||||||
|  |         # if it still fails after 3 retrys, then fail the job | ||||||
|  |         exit 1 | ||||||
|  |     fi | ||||||
|  | fi | ||||||
|  | exit 0 | ||||||
							
								
								
									
										20
									
								
								.github/ci-cd-scripts/playwright-electron.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,17 +1,15 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| # bash strict mode | # bash strict mode | ||||||
| set -euo pipefail | set -euo pipefail | ||||||
|  |  | ||||||
| if [[ ! -f "test-results/.last-run.json" ]]; then | if [[ ! -f "test-results/.last-run.json" ]]; then | ||||||
|     # if no last run artifact, than run plawright normally |     # if no last run artifact, than run plawright normally | ||||||
|     echo "run playwright normally" |     echo "run playwright normally" | ||||||
|         if [[ "$3" == *ubuntu* ]]; then |         if [[ "$1" == ubuntu-latest* ]]; then | ||||||
|             xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true |             xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true | ||||||
|         elif [[ "$3" == *windows* ]]; then |         elif [[ "$1" == windows-latest* ]]; then | ||||||
|             yarn test:playwright:electron:windows -- --shard=$1/$2 || true |             yarn test:playwright:electron:windows || true | ||||||
|         elif [[ "$3" == *macos* ]]; then |         elif [[ "$1" == macos-14* ]]; then | ||||||
|             yarn test:playwright:electron:macos  -- --shard=$1/$2 || true |             yarn test:playwright:electron:macos || true | ||||||
|         else |         else | ||||||
|             echo "Do not run playwright. Unable to detect os runtime." |             echo "Do not run playwright. Unable to detect os runtime." | ||||||
|             exit 1 |             exit 1 | ||||||
| @ -30,11 +28,11 @@ while [[ $retry -le $max_retrys ]]; do | |||||||
|         if [[ $failed_tests -gt 0 ]]; then |         if [[ $failed_tests -gt 0 ]]; then | ||||||
|             echo "retried=true" >>$GITHUB_OUTPUT |             echo "retried=true" >>$GITHUB_OUTPUT | ||||||
|             echo "run playwright with last failed tests and retry $retry" |             echo "run playwright with last failed tests and retry $retry" | ||||||
|             if [[ "$3" == *ubuntu* ]]; then |             if [[ "$1" == ubuntu-latest* ]]; then | ||||||
|                 xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true |                 xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true | ||||||
|             elif [[ "$3" == *windows* ]]; then |             elif [[ "$1" == windows-latest* ]]; then | ||||||
|                 yarn test:playwright:electron:windows -- --last-failed || true |                 yarn test:playwright:electron:windows -- --last-failed || true | ||||||
|             elif [[ "$3" == *macos* ]]; then |             elif [[ "$1" == macos-14* ]]; then | ||||||
|                 yarn test:playwright:electron:macos -- --last-failed || true |                 yarn test:playwright:electron:macos -- --last-failed || true | ||||||
|             else |             else | ||||||
|                 echo "Do not run playwright. Unable to detect os runtime." |                 echo "Do not run playwright. Unable to detect os runtime." | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/workflows/build-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -165,6 +165,7 @@ jobs: | |||||||
|       - name: Build the app (release) |       - name: Build the app (release) | ||||||
|         if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }} |         if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }} | ||||||
|         env: |         env: | ||||||
|  |           PUBLISH_FOR_PULL_REQUEST: true | ||||||
|           APPLE_ID: ${{ secrets.APPLE_ID }} |           APPLE_ID: ${{ secrets.APPLE_ID }} | ||||||
|           APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} |           APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | ||||||
|           APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} |           APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | ||||||
| @ -172,6 +173,7 @@ jobs: | |||||||
|           CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} |           CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} | ||||||
|           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} |           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||||||
|           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} |           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||||||
|  |           CSC_FOR_PULL_REQUEST: true | ||||||
|           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} |           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} | ||||||
|         run: yarn electron-builder --config --publish always |         run: yarn electron-builder --config --publish always | ||||||
|  |  | ||||||
| @ -227,6 +229,7 @@ jobs: | |||||||
|           CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} |           CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} | ||||||
|           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} |           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||||||
|           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} |           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||||||
|  |           CSC_FOR_PULL_REQUEST: true | ||||||
|           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} |           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} | ||||||
|         run: yarn electron-builder --config --publish always |         run: yarn electron-builder --config --publish always | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								.github/workflows/cargo-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -2,8 +2,28 @@ on: | |||||||
|   push: |   push: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |     paths: | ||||||
|  |       - 'src/wasm-lib/**.rs' | ||||||
|  |       - 'src/wasm-lib/**.hbs' | ||||||
|  |       - 'src/wasm-lib/**.gen' | ||||||
|  |       - 'src/wasm-lib/**.snap' | ||||||
|  |       - '**/Cargo.toml' | ||||||
|  |       - '**/Cargo.lock' | ||||||
|  |       - '**/rust-toolchain.toml' | ||||||
|  |       - 'src/wasm-lib/**.kcl' | ||||||
|  |       - .github/workflows/cargo-test.yml | ||||||
|  |  | ||||||
|   pull_request: |   pull_request: | ||||||
|  |     paths: | ||||||
|  |       - 'src/wasm-lib/**.rs' | ||||||
|  |       - 'src/wasm-lib/**.hbs' | ||||||
|  |       - 'src/wasm-lib/**.gen' | ||||||
|  |       - 'src/wasm-lib/**.snap' | ||||||
|  |       - '**/Cargo.toml' | ||||||
|  |       - '**/Cargo.lock' | ||||||
|  |       - '**/rust-toolchain.toml' | ||||||
|  |       - 'src/wasm-lib/**.kcl' | ||||||
|  |       - .github/workflows/cargo-test.yml | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
| permissions: read-all | permissions: read-all | ||||||
| concurrency: | concurrency: | ||||||
| @ -51,7 +71,7 @@ jobs: | |||||||
|           KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} |           KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}} | ||||||
|           RUST_MIN_STACK: 10485760000 |           RUST_MIN_STACK: 10485760000 | ||||||
|       - name: Upload to codecov.io |       - name: Upload to codecov.io | ||||||
|         uses: codecov/codecov-action@v5 |         uses: codecov/codecov-action@v4 | ||||||
|         with: |         with: | ||||||
|           token: ${{secrets.CODECOV_TOKEN}} |           token: ${{secrets.CODECOV_TOKEN}} | ||||||
|           fail_ci_if_error: true |           fail_ci_if_error: true | ||||||
|  | |||||||
							
								
								
									
										164
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -18,7 +18,6 @@ permissions: | |||||||
| jobs: | jobs: | ||||||
|  |  | ||||||
|   check-rust-changes: |   check-rust-changes: | ||||||
|     if: github.event.pull_request.draft == false |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     outputs: |     outputs: | ||||||
|       rust-changed: ${{ steps.filter.outputs.rust }} |       rust-changed: ${{ steps.filter.outputs.rust }} | ||||||
| @ -34,20 +33,20 @@ jobs: | |||||||
|             rust: |             rust: | ||||||
|               - 'src/wasm-lib/**' |               - 'src/wasm-lib/**' | ||||||
|  |  | ||||||
|   electron: |   browser: | ||||||
|     if: github.event.pull_request.draft == false |     timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }} | ||||||
|     timeout-minutes: 60 |     name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} | ||||||
|     name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} |  | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         # TODO: enable self-hosted-windows-8-cores once available |         os: [ubuntu-latest-8-cores, windows-latest-8-cores] | ||||||
|         os: [namespace-profile-ubuntu-8-cores, namespace-profile-macos-8-cores, windows-16-cores] |  | ||||||
|         shardIndex: [1, 2, 3, 4] |         shardIndex: [1, 2, 3, 4] | ||||||
|         shardTotal: [4] |         shardTotal: [4] | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
|     needs: check-rust-changes |     needs: check-rust-changes | ||||||
|     steps: |     steps: | ||||||
|  |     - name: Tune GitHub-hosted runner network | ||||||
|  |       uses: smorimoto/tune-github-hosted-runner-network@v1 | ||||||
|     - uses: actions/checkout@v4 |     - uses: actions/checkout@v4 | ||||||
|     - uses: actions/setup-node@v4 |     - uses: actions/setup-node@v4 | ||||||
|       with: |       with: | ||||||
| @ -102,8 +101,7 @@ jobs: | |||||||
|         echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH |         echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH | ||||||
|     - name: Install vector |     - name: Install vector | ||||||
|       shell: bash |       shell: bash | ||||||
|       # TODO: figure out what to do with this, it's failing |       if:  ${{ !startsWith(matrix.os, 'windows') }} | ||||||
|       if: false |  | ||||||
|       run: | |       run: | | ||||||
|         curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh |         curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh | ||||||
|         chmod +x /tmp/vector.sh |         chmod +x /tmp/vector.sh | ||||||
| @ -125,13 +123,13 @@ jobs: | |||||||
|       if: steps.download-wasm.outcome == 'failure' |       if: steps.download-wasm.outcome == 'failure' | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: yarn build:wasm |       run: yarn build:wasm | ||||||
|     - name: build electron |     - name: build web | ||||||
|  |       run: yarn build:local | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: yarn tron:package |  | ||||||
|     - name: Run ubuntu/chrome snapshots |     - name: Run ubuntu/chrome snapshots | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: | |       run: | | ||||||
|         PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} |         yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | ||||||
|       env: |       env: | ||||||
|         CI: true |         CI: true | ||||||
|         NODE_ENV: development |         NODE_ENV: development | ||||||
| @ -188,12 +186,12 @@ jobs: | |||||||
|       with: |       with: | ||||||
|         name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} |         name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||||
|         path: test-results/ |         path: test-results/ | ||||||
|     - name: Run playwright/electron flow (with retries) |     - name: Run playwright/chrome flow (with retries) | ||||||
|       id: retry |       id: retry | ||||||
|       if: ${{ !cancelled() && (success() || failure()) }} |       if: ${{ !cancelled() && (success() || failure()) }} | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: | |       run: | | ||||||
|         .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}} |         .github/ci-cd-scripts/playwright-browser-chrome.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}} | ||||||
|       env: |       env: | ||||||
|         CI: true |         CI: true | ||||||
|         FAIL_ON_CONSOLE_ERRORS: true |         FAIL_ON_CONSOLE_ERRORS: true | ||||||
| @ -201,6 +199,11 @@ jobs: | |||||||
|         VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} |         VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||||
|         VITE_KC_SKIP_AUTH: true |         VITE_KC_SKIP_AUTH: true | ||||||
|         token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} |         token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||||
|  |     - name: send to axiom | ||||||
|  |       if: always() | ||||||
|  |       shell: bash | ||||||
|  |       run: | | ||||||
|  |         node playwrightProcess.mjs | tee /tmp/github-actions.log | ||||||
|     - uses: actions/upload-artifact@v4 |     - uses: actions/upload-artifact@v4 | ||||||
|       if: always() |       if: always() | ||||||
|       with: |       with: | ||||||
| @ -218,3 +221,136 @@ jobs: | |||||||
|         retention-days: 30 |         retention-days: 30 | ||||||
|         overwrite: true |         overwrite: true | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   electron: | ||||||
|  |     name: playwright:electron:${{matrix.os}} | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large] | ||||||
|  |     timeout-minutes: 60 | ||||||
|  |     runs-on: ${{ matrix.os }} | ||||||
|  |     needs: check-rust-changes | ||||||
|  |     steps: | ||||||
|  |     - name: Tune GitHub-hosted runner network | ||||||
|  |       uses: smorimoto/tune-github-hosted-runner-network@v1 | ||||||
|  |     - uses: actions/checkout@v4 | ||||||
|  |     - uses: actions/setup-node@v4 | ||||||
|  |       with: | ||||||
|  |         node-version-file: '.nvmrc' | ||||||
|  |         cache: 'yarn' | ||||||
|  |     - uses: KittyCAD/action-install-cli@main | ||||||
|  |     - name: Install dependencies | ||||||
|  |       shell: bash | ||||||
|  |       run: yarn | ||||||
|  |     - name: Cache Playwright Browsers | ||||||
|  |       uses: actions/cache@v4 | ||||||
|  |       with: | ||||||
|  |         path: | | ||||||
|  |           ~/.cache/ms-playwright/ | ||||||
|  |         key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }} | ||||||
|  |     - name: Install Playwright Browsers | ||||||
|  |       shell: bash | ||||||
|  |       run: yarn playwright install chromium --with-deps | ||||||
|  |     - name: Download Wasm Cache | ||||||
|  |       id: download-wasm | ||||||
|  |       if: needs.check-rust-changes.outputs.rust-changed == 'false' | ||||||
|  |       uses: dawidd6/action-download-artifact@v7 | ||||||
|  |       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' | ||||||
|  |       shell: bash | ||||||
|  |       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' | ||||||
|  |       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: install good sed | ||||||
|  |       if:  ${{ startsWith(matrix.os, 'macos') }} | ||||||
|  |       shell: bash | ||||||
|  |       run: | | ||||||
|  |         brew install gnu-sed | ||||||
|  |         echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH | ||||||
|  |     - name: Install vector | ||||||
|  |       if:  ${{ startsWith(matrix.os, 'ubuntu') }} | ||||||
|  |       shell: bash | ||||||
|  |       run: | | ||||||
|  |         curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh | ||||||
|  |         chmod +x /tmp/vector.sh | ||||||
|  |         /tmp/vector.sh -y -no-modify-path | ||||||
|  |         mkdir -p /tmp/vector | ||||||
|  |         cp .github/workflows/vector.toml /tmp/vector.toml | ||||||
|  |         sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml | ||||||
|  |         sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml | ||||||
|  |         sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml | ||||||
|  |         sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml | ||||||
|  |         sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml | ||||||
|  |         cat /tmp/vector.toml | ||||||
|  |         ${HOME}/.vector/bin/vector --config /tmp/vector.toml & | ||||||
|  |     - name: Build Wasm (because rust diff) | ||||||
|  |       if: needs.check-rust-changes.outputs.rust-changed == 'true' | ||||||
|  |       shell: bash | ||||||
|  |       run: yarn build:wasm | ||||||
|  |     - name: OR Build Wasm (because wasm cache failed) | ||||||
|  |       if: steps.download-wasm.outcome == 'failure' | ||||||
|  |       shell: bash | ||||||
|  |       run: yarn build:wasm | ||||||
|  |     - name: build electron | ||||||
|  |       shell: bash | ||||||
|  |       run: yarn tron:package | ||||||
|  |     - uses: actions/download-artifact@v4 | ||||||
|  |       if: ${{ !cancelled() && (success() || failure()) }} | ||||||
|  |       continue-on-error: true | ||||||
|  |       with: | ||||||
|  |         name: test-results-electron-${{ matrix.os }}-${{ github.sha }} | ||||||
|  |         path: test-results/ | ||||||
|  |     - name: Run electron tests (with retries) | ||||||
|  |       id: retry | ||||||
|  |       if: ${{ !cancelled() && (success() || failure()) }} | ||||||
|  |       shell: bash | ||||||
|  |       run: | | ||||||
|  |         .github/ci-cd-scripts/playwright-electron.sh ${{ matrix.os }} | ||||||
|  |       env: | ||||||
|  |         CI: true | ||||||
|  |         FAIL_ON_CONSOLE_ERRORS: true | ||||||
|  |         NODE_ENV: development | ||||||
|  |         VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||||
|  |         VITE_KC_SKIP_AUTH: true | ||||||
|  |         IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }} | ||||||
|  |         #DEBUG: 'pw:browser*' | ||||||
|  |     - name: send to axiom | ||||||
|  |       if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }} | ||||||
|  |       shell: bash | ||||||
|  |       run: | | ||||||
|  |         node playwrightProcess.mjs | tee /tmp/github-actions.log | ||||||
|  |     - uses: actions/upload-artifact@v4 | ||||||
|  |       if: ${{ !cancelled() && (success() || failure()) }} | ||||||
|  |       with: | ||||||
|  |         name: test-results-electron-${{ matrix.os }}-${{ github.sha }} | ||||||
|  |         path: test-results/ | ||||||
|  |         include-hidden-files: true | ||||||
|  |         retention-days: 30 | ||||||
|  |         overwrite: true | ||||||
|  |     - uses: actions/upload-artifact@v4 | ||||||
|  |       if: ${{ !cancelled() && (success() || failure()) }} | ||||||
|  |       with: | ||||||
|  |         name: playwright-report-electron-${{ matrix.os }}-${{ github.sha }} | ||||||
|  |         path: playwright-report/ | ||||||
|  |         include-hidden-files: true | ||||||
|  |         retention-days: 30 | ||||||
|  |         overwrite: true | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -388,6 +388,23 @@ yarn test:unit:local | |||||||
|  |  | ||||||
| #### E2E Tests | #### E2E Tests | ||||||
|  |  | ||||||
|  | **Playwright Browser** | ||||||
|  |  | ||||||
|  | These E2E tests run in a browser (without electron). | ||||||
|  | There are tests that are skipped if they are ran in a windows OS or Linux OS. We can use playwright tags to implement test skipping. | ||||||
|  |  | ||||||
|  | Breaking down the command `yarn test:playwright:browser:chrome:windows` | ||||||
|  | - The application is `playwright` | ||||||
|  | - The runtime is a `browser` | ||||||
|  | - The specific `browser` is `chrome` | ||||||
|  | - The test should run in a `windows` environment. It will skip tests that are broken or flaky in the windows OS. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | yarn test:playwright:browser:chrome | ||||||
|  | yarn test:playwright:browser:chrome:windows | ||||||
|  | yarn test:playwright:browser:chrome:ubuntu | ||||||
|  | ``` | ||||||
|  |  | ||||||
| **Playwright Electron** | **Playwright Electron** | ||||||
|  |  | ||||||
| These E2E tests run in electron. There are tests that are skipped if they are ran in a windows, linux, or macos environment. We can use playwright tags to implement test skipping. | These E2E tests run in electron. There are tests that are skipped if they are ran in a windows, linux, or macos environment. We can use playwright tags to implement test skipping. | ||||||
|  | |||||||
| @ -22,5 +22,3 @@ once fixed in engine will just start working here with no language changes. | |||||||
|  |  | ||||||
| - **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple | - **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple | ||||||
|     chamfer cases work currently. |     chamfer cases work currently. | ||||||
|  |  | ||||||
| - **Appearance**: Changing the appearance on a loft does not work. |  | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ layout: manual | |||||||
| * [`angledLineThatIntersects`](kcl/angledLineThatIntersects) | * [`angledLineThatIntersects`](kcl/angledLineThatIntersects) | ||||||
| * [`angledLineToX`](kcl/angledLineToX) | * [`angledLineToX`](kcl/angledLineToX) | ||||||
| * [`angledLineToY`](kcl/angledLineToY) | * [`angledLineToY`](kcl/angledLineToY) | ||||||
| * [`appearance`](kcl/appearance) |  | ||||||
| * [`arc`](kcl/arc) | * [`arc`](kcl/arc) | ||||||
| * [`arcTo`](kcl/arcTo) | * [`arcTo`](kcl/arcTo) | ||||||
| * [`asin`](kcl/asin) | * [`asin`](kcl/asin) | ||||||
| @ -30,7 +29,6 @@ layout: manual | |||||||
| * [`assertLessThan`](kcl/assertLessThan) | * [`assertLessThan`](kcl/assertLessThan) | ||||||
| * [`assertLessThanOrEq`](kcl/assertLessThanOrEq) | * [`assertLessThanOrEq`](kcl/assertLessThanOrEq) | ||||||
| * [`atan`](kcl/atan) | * [`atan`](kcl/atan) | ||||||
| * [`atan2`](kcl/atan2) |  | ||||||
| * [`bezierCurve`](kcl/bezierCurve) | * [`bezierCurve`](kcl/bezierCurve) | ||||||
| * [`ceil`](kcl/ceil) | * [`ceil`](kcl/ceil) | ||||||
| * [`chamfer`](kcl/chamfer) | * [`chamfer`](kcl/chamfer) | ||||||
| @ -101,8 +99,8 @@ layout: manual | |||||||
| * [`sin`](kcl/sin) | * [`sin`](kcl/sin) | ||||||
| * [`sqrt`](kcl/sqrt) | * [`sqrt`](kcl/sqrt) | ||||||
| * [`startProfileAt`](kcl/startProfileAt) | * [`startProfileAt`](kcl/startProfileAt) | ||||||
|  | * [`startSketchAt`](kcl/startSketchAt) | ||||||
| * [`startSketchOn`](kcl/startSketchOn) | * [`startSketchOn`](kcl/startSketchOn) | ||||||
| * [`sweep`](kcl/sweep) |  | ||||||
| * [`tan`](kcl/tan) | * [`tan`](kcl/tan) | ||||||
| * [`tangentToEnd`](kcl/tangentToEnd) | * [`tangentToEnd`](kcl/tangentToEnd) | ||||||
| * [`tangentialArc`](kcl/tangentialArc) | * [`tangentialArc`](kcl/tangentialArc) | ||||||
|  | |||||||
| @ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object | |||||||
|    - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local") |    - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local") | ||||||
|  |  | ||||||
| ```js | ```js | ||||||
| patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid] | patternTransform(total_instances: u32, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid] | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -43,7 +43,7 @@ patternTransform(total_instances: integer, transform_function: FunctionParam, so | |||||||
|  |  | ||||||
| | Name | Type | Description | Required | | | Name | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `total_instances` | `integer` |  | Yes | | | `total_instances` | `u32` |  | Yes | | ||||||
| | `transform_function` | `FunctionParam` |  | Yes | | | `transform_function` | `FunctionParam` |  | Yes | | ||||||
| | `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes | | | `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes | | ||||||
|  |  | ||||||
| @ -95,8 +95,7 @@ fn cube(length, center) { | |||||||
|   p2 = [l + x, l + y] |   p2 = [l + x, l + y] | ||||||
|   p3 = [l + x, -l + y] |   p3 = [l + x, -l + y] | ||||||
|  |  | ||||||
|   return startSketchOn('XY') |   return startSketchAt(p0) | ||||||
|     |> startProfileAt(p0, %) |  | ||||||
|     |> lineTo(p1, %) |     |> lineTo(p1, %) | ||||||
|     |> lineTo(p2, %) |     |> lineTo(p2, %) | ||||||
|     |> lineTo(p3, %) |     |> lineTo(p3, %) | ||||||
| @ -133,8 +132,7 @@ fn cube(length, center) { | |||||||
|   p2 = [l + x, l + y] |   p2 = [l + x, l + y] | ||||||
|   p3 = [l + x, -l + y] |   p3 = [l + x, -l + y] | ||||||
|  |  | ||||||
|   return startSketchOn('XY') |   return startSketchAt(p0) | ||||||
|     |> startProfileAt(p0, %) |  | ||||||
|     |> lineTo(p1, %) |     |> lineTo(p1, %) | ||||||
|     |> lineTo(p2, %) |     |> lineTo(p2, %) | ||||||
|     |> lineTo(p3, %) |     |> lineTo(p3, %) | ||||||
| @ -197,8 +195,7 @@ fn transform(i) { | |||||||
|     { rotation = { angle = 45 * i } } |     { rotation = { angle = 45 * i } } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
| startSketchOn('XY') | startSketchAt([0, 0]) | ||||||
|   |> startProfileAt([0, 0], %) |  | ||||||
|   |> polygon({ |   |> polygon({ | ||||||
|        radius = 10, |        radius = 10, | ||||||
|        numSides = 4, |        numSides = 4, | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids. | |||||||
|  |  | ||||||
|  |  | ||||||
| ```js | ```js | ||||||
| patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch] | patternTransform2d(total_instances: u32, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch] | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -17,7 +17,7 @@ patternTransform2d(total_instances: integer, transform_function: FunctionParam, | |||||||
|  |  | ||||||
| | Name | Type | Description | Required | | | Name | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `total_instances` | `integer` |  | Yes | | | `total_instances` | `u32` |  | Yes | | ||||||
| | `transform_function` | `FunctionParam` |  | Yes | | | `transform_function` | `FunctionParam` |  | Yes | | ||||||
| | `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | | `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | ||||||
|  |  | ||||||
|  | |||||||
| @ -43,7 +43,7 @@ fn sum(arr) { | |||||||
|  |  | ||||||
| /* The above is basically like this pseudo-code: | /* The above is basically like this pseudo-code: | ||||||
| fn sum(arr): | fn sum(arr): | ||||||
|     sumSoFar = 0 |     let sumSoFar = 0 | ||||||
|     for i in arr: |     for i in arr: | ||||||
|         sumSoFar = add(sumSoFar, i) |         sumSoFar = add(sumSoFar, i) | ||||||
|     return sumSoFar */ |     return sumSoFar */ | ||||||
| @ -79,8 +79,7 @@ fn decagon(radius) { | |||||||
|   stepAngle = 1 / 10 * tau() |   stepAngle = 1 / 10 * tau() | ||||||
|  |  | ||||||
|   // Start the decagon sketch at this point. |   // Start the decagon sketch at this point. | ||||||
|   startOfDecagonSketch = startSketchOn('XY') |   startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius]) | ||||||
|     |> startProfileAt([cos(0) * radius, sin(0) * radius], %) |  | ||||||
|  |  | ||||||
|   // Use a `reduce` to draw the remaining decagon sides. |   // Use a `reduce` to draw the remaining decagon sides. | ||||||
|   // For each number in the array 1..10, run the given function, |   // For each number in the array 1..10, run the given function, | ||||||
| @ -97,15 +96,14 @@ fn decagon(radius) { | |||||||
|  |  | ||||||
| /* The `decagon` above is basically like this pseudo-code: | /* The `decagon` above is basically like this pseudo-code: | ||||||
| fn decagon(radius): | fn decagon(radius): | ||||||
|     stepAngle = (1/10) * tau() |     let stepAngle = (1/10) * tau() | ||||||
|     plane = startSketchOn('XY') |     let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)]) | ||||||
|     startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane) |  | ||||||
|  |  | ||||||
|     // Here's the reduce part. |     // Here's the reduce part. | ||||||
|     partialDecagon = startOfDecagonSketch |     let partialDecagon = startOfDecagonSketch | ||||||
|     for i in [1..10]: |     for i in [1..10]: | ||||||
|         x = cos(stepAngle * i) * radius |         let x = cos(stepAngle * i) * radius | ||||||
|         y = sin(stepAngle * i) * radius |         let y = sin(stepAngle * i) * radius | ||||||
|         partialDecagon = lineTo([x, y], partialDecagon) |         partialDecagon = lineTo([x, y], partialDecagon) | ||||||
|     fullDecagon = partialDecagon // it's now full |     fullDecagon = partialDecagon // it's now full | ||||||
|     return fullDecagon */ |     return fullDecagon */ | ||||||
|  | |||||||
| @ -28,8 +28,7 @@ segEnd(tag: TagIdentifier) -> [number] | |||||||
|  |  | ||||||
| ```js | ```js | ||||||
| w = 15 | w = 15 | ||||||
| cube = startSketchOn('XY') | cube = startSketchAt([0, 0]) | ||||||
|   |> startProfileAt([0, 0], %) |  | ||||||
|   |> line([w, 0], %, $line1) |   |> line([w, 0], %, $line1) | ||||||
|   |> line([0, w], %, $line2) |   |> line([0, w], %, $line2) | ||||||
|   |> line([-w, 0], %, $line3) |   |> line([-w, 0], %, $line3) | ||||||
| @ -38,8 +37,7 @@ cube = startSketchOn('XY') | |||||||
|   |> extrude(5, %) |   |> extrude(5, %) | ||||||
|  |  | ||||||
| fn cylinder(radius, tag) { | fn cylinder(radius, tag) { | ||||||
|   return startSketchOn('XY') |   return startSketchAt([0, 0]) | ||||||
|     |> startProfileAt([0, 0], %) |  | ||||||
|     |> circle({ |     |> circle({ | ||||||
|          radius = radius, |          radius = radius, | ||||||
|          center = segEnd(tag) |          center = segEnd(tag) | ||||||
|  | |||||||
| @ -28,8 +28,7 @@ segStart(tag: TagIdentifier) -> [number] | |||||||
|  |  | ||||||
| ```js | ```js | ||||||
| w = 15 | w = 15 | ||||||
| cube = startSketchOn('XY') | cube = startSketchAt([0, 0]) | ||||||
|   |> startProfileAt([0, 0], %) |  | ||||||
|   |> line([w, 0], %, $line1) |   |> line([w, 0], %, $line1) | ||||||
|   |> line([0, w], %, $line2) |   |> line([0, w], %, $line2) | ||||||
|   |> line([-w, 0], %, $line3) |   |> line([-w, 0], %, $line3) | ||||||
| @ -38,8 +37,7 @@ cube = startSketchOn('XY') | |||||||
|   |> extrude(5, %) |   |> extrude(5, %) | ||||||
|  |  | ||||||
| fn cylinder(radius, tag) { | fn cylinder(radius, tag) { | ||||||
|   return startSketchOn('XY') |   return startSketchAt([0, 0]) | ||||||
|     |> startProfileAt([0, 0], %) |  | ||||||
|     |> circle({ |     |> circle({ | ||||||
|          radius = radius, |          radius = radius, | ||||||
|          center = segStart(tag) |          center = segStart(tag) | ||||||
|  | |||||||
| @ -4,8 +4,6 @@ excerpt: "Start a new 2-dimensional sketch at a given point on the 'XY' plane." | |||||||
| layout: manual | layout: manual | ||||||
| --- | --- | ||||||
|  |  | ||||||
| **WARNING:** This function is deprecated. |  | ||||||
|  |  | ||||||
| Start a new 2-dimensional sketch at a given point on the 'XY' plane. | Start a new 2-dimensional sketch at a given point on the 'XY' plane. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										14246
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						| @ -13,18 +13,13 @@ Data to draw an angled line. | |||||||
|  |  | ||||||
| An angle and length with explicitly named parameters | An angle and length with explicitly named parameters | ||||||
|  |  | ||||||
| **Type:** `object` | [`PolarCoordsData`](/docs/kcl/types/PolarCoordsData) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `angle` |`number`| The angle of the line (in degrees). | No | |  | ||||||
| | `length` |`number`| The length of the line. | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  | |||||||
| @ -1,23 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "AppearanceData" |  | ||||||
| excerpt: "Data for appearance." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| Data for appearance. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `color` |`string`| Color of the new material, a hex string like "#ff0000". | No | |  | ||||||
| | `metalness` |`number` (**maximum:** 100.0)| Metalness of the new material, a percentage like 95.7. | No | |  | ||||||
| | `roughness` |`number` (**maximum:** 100.0)| Roughness of the new material, a percentage like 95.7. | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -12,10 +12,5 @@ KCL value for an optional parameter which was not given an argument. (remember, | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -329,23 +329,6 @@ Data for an imported geometry. | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `type` |enum: `Module`|  | No | |  | ||||||
| | `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Any KCL value. | No | |  | ||||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties | ## Properties | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
|  | |||||||
| @ -1,16 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "ModuleId" |  | ||||||
| excerpt: "Identifier of a source file.  Uses a u32 to keep the size small." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| Identifier of a source file.  Uses a u32 to keep the size small. |  | ||||||
|  |  | ||||||
| **Type:** `integer` (`uint32`) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| --- |  | ||||||
| title: "SweepData" |  | ||||||
| excerpt: "Data for a sweep." |  | ||||||
| layout: manual |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| Data for a sweep. |  | ||||||
|  |  | ||||||
| **Type:** `object` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Properties |  | ||||||
|  |  | ||||||
| | Property | Type | Description | Required | |  | ||||||
| |----------|------|-------------|----------| |  | ||||||
| | `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No | |  | ||||||
| | `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No | |  | ||||||
| | `tolerance` |`number`| Tolerance for the sweep operation. | No | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -1,11 +1,22 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
|  |  | ||||||
|  | import { setupElectron, tearDown } from './test-utils' | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Electron app header tests', () => { | test.describe('Electron app header tests', () => { | ||||||
|   test( |   test( | ||||||
|     'Open Command Palette button has correct shortcut', |     'Open Command Palette button has correct shortcut', | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ page }, testInfo) => { |     async ({ browserName }, testInfo) => { | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async () => {}, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       // No space before the shortcut since it checks textContent. |       // No space before the shortcut since it checks textContent. | ||||||
|       let text |       let text | ||||||
| @ -23,14 +34,21 @@ test.describe('Electron app header tests', () => { | |||||||
|       const commandsButton = page.getByRole('button', { name: 'Commands' }) |       const commandsButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|       await expect(commandsButton).toBeVisible() |       await expect(commandsButton).toBeVisible() | ||||||
|       await expect(commandsButton).toHaveText(text) |       await expect(commandsButton).toHaveText(text) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   test( |   test( | ||||||
|     'User settings has correct shortcut', |     'User settings has correct shortcut', | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ page }, testInfo) => { |     async ({ browserName }, testInfo) => { | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async () => {}, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       // Open the user sidebar menu. |       // Open the user sidebar menu. | ||||||
|       await page.getByTestId('user-sidebar-toggle').click() |       await page.getByTestId('user-sidebar-toggle').click() | ||||||
| @ -41,6 +59,8 @@ test.describe('Electron app header tests', () => { | |||||||
|       const userSettingsButton = page.getByTestId('user-settings') |       const userSettingsButton = page.getByTestId('user-settings') | ||||||
|       await expect(userSettingsButton).toBeVisible() |       await expect(userSettingsButton).toBeVisible() | ||||||
|       await expect(userSettingsButton).toHaveText(text) |       await expect(userSettingsButton).toHaveText(text) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -1,26 +1,29 @@ | |||||||
| import { test, expect, Page } from './zoo-test' | import { test, expect, Page } from '@playwright/test' | ||||||
| import { | import { | ||||||
|   getUtils, |   getUtils, | ||||||
|   TEST_COLORS, |   TEST_COLORS, | ||||||
|  |   setup, | ||||||
|  |   tearDown, | ||||||
|   commonPoints, |   commonPoints, | ||||||
|   PERSIST_MODELING_CONTEXT, |   PERSIST_MODELING_CONTEXT, | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
| import { HomePageFixture } from './fixtures/homePageFixture' |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.setTimeout(120000) | test.setTimeout(120000) | ||||||
|  |  | ||||||
| async function doBasicSketch( | async function doBasicSketch(page: Page, openPanes: string[]) { | ||||||
|   page: Page, |  | ||||||
|   homePage: HomePageFixture, |  | ||||||
|   openPanes: string[] |  | ||||||
| ) { |  | ||||||
|   const u = await getUtils(page) |   const u = await getUtils(page) | ||||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) |   await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio |   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|  |  | ||||||
|   await homePage.goToModelingScene() |   await u.waitForAuthSkipAppStart() | ||||||
|   await u.waitForPageLoad() |  | ||||||
|   await page.waitForTimeout(1000) |  | ||||||
|   await u.openDebugPanel() |   await u.openDebugPanel() | ||||||
|  |  | ||||||
|   // If we have the code pane open, we should see the code. |   // If we have the code pane open, we should see the code. | ||||||
| @ -145,11 +148,13 @@ async function doBasicSketch( | |||||||
| } | } | ||||||
|  |  | ||||||
| test.describe('Basic sketch', () => { | test.describe('Basic sketch', () => { | ||||||
|   test.fixme('code pane open at start', async ({ page, homePage }) => { |   test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => { | ||||||
|     await doBasicSketch(page, homePage, ['code']) |     // Skip on windows it is being weird. | ||||||
|  |     test.skip(process.platform === 'win32', 'Skip on windows') | ||||||
|  |     await doBasicSketch(page, ['code']) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('code pane closed at start', async ({ page, homePage }) => { |   test('code pane closed at start', async ({ page }) => { | ||||||
|     // Load the app with the code panes |     // Load the app with the code panes | ||||||
|     await page.addInitScript(async (persistModelingContext) => { |     await page.addInitScript(async (persistModelingContext) => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -157,6 +162,6 @@ test.describe('Basic sketch', () => { | |||||||
|         JSON.stringify({ openPanes: [] }) |         JSON.stringify({ openPanes: [] }) | ||||||
|       ) |       ) | ||||||
|     }, PERSIST_MODELING_CONTEXT) |     }, PERSIST_MODELING_CONTEXT) | ||||||
|     await doBasicSketch(page, homePage, []) |     await doBasicSketch(page, []) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -1,21 +1,27 @@ | |||||||
| import { test, expect, Page } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
| import { HomePageFixture } from './fixtures/homePageFixture' | import { getUtils, setup, tearDown } from './test-utils' | ||||||
| import { getUtils } from './test-utils' |  | ||||||
| import { EngineCommand } from 'lang/std/artifactGraph' | import { EngineCommand } from 'lang/std/artifactGraph' | ||||||
| import { uuidv4 } from 'lib/utils' | import { uuidv4 } from 'lib/utils' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Can create sketches on all planes and their back sides', () => { | test.describe('Can create sketches on all planes and their back sides', () => { | ||||||
|   const sketchOnPlaneAndBackSideTest = async ( |   const sketchOnPlaneAndBackSideTest = async ( | ||||||
|     page: Page, |     page: any, | ||||||
|     homePage: HomePageFixture, |  | ||||||
|     plane: string, |     plane: string, | ||||||
|     clickCoords: { x: number; y: number } |     clickCoords: { x: number; y: number } | ||||||
|   ) => { |   ) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio |     const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|  |  | ||||||
|     const coord = |     const coord = | ||||||
| @ -77,39 +83,32 @@ test.describe('Can create sketches on all planes and their back sides', () => { | |||||||
|     await u.clearCommandLogs() |     await u.clearCommandLogs() | ||||||
|     await u.removeCurrentCode() |     await u.removeCurrentCode() | ||||||
|   } |   } | ||||||
|   test('XY', async ({ page, homePage }) => { |   test('XY', async ({ page }) => { | ||||||
|     await sketchOnPlaneAndBackSideTest( |     await sketchOnPlaneAndBackSideTest( | ||||||
|       page, |       page, | ||||||
|       homePage, |  | ||||||
|       'XY', |       'XY', | ||||||
|       { x: 600, y: 388 } // red plane |       { x: 600, y: 388 } // red plane | ||||||
|       // { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too. |       // { x: 600, y: 400 }, // red plane // clicks grid helper and that causes problems, should fix so that these coords work too. | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('YZ', async ({ page, homePage }) => { |   test('YZ', async ({ page }) => { | ||||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', { x: 700, y: 250 }) // green plane |     await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('XZ', async ({ page, homePage }) => { |   test('XZ', async ({ page }) => { | ||||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', { x: 700, y: 80 }) // blue plane |     await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('-XY', async ({ page, homePage }) => { |   test('-XY', async ({ page }) => { | ||||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', { |     await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane | ||||||
|       x: 600, |  | ||||||
|       y: 118, |  | ||||||
|     }) // back of red plane |  | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('-YZ', async ({ page, homePage }) => { |   test('-YZ', async ({ page }) => { | ||||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', { |     await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane | ||||||
|       x: 700, |  | ||||||
|       y: 219, |  | ||||||
|     }) // back of green plan |  | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('-XZ', async ({ page, homePage }) => { |   test('-XZ', async ({ page }) => { | ||||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', { x: 700, y: 427 }) // back of blue plane |     await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -1,15 +1,28 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
|  |  | ||||||
| import { getUtils, executorInputPath } from './test-utils' | import { | ||||||
|  |   getUtils, | ||||||
|  |   setup, | ||||||
|  |   setupElectron, | ||||||
|  |   tearDown, | ||||||
|  |   executorInputPath, | ||||||
|  | } from './test-utils' | ||||||
| import { join } from 'path' | import { join } from 'path' | ||||||
| import { bracket } from 'lib/exampleKcl' | import { bracket } from 'lib/exampleKcl' | ||||||
| import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' | import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Code pane and errors', () => { | test.describe('Code pane and errors', () => { | ||||||
|   test('Typing KCL errors induces a badge on the code pane button', async ({ |   test('Typing KCL errors induces a badge on the code pane button', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|  |  | ||||||
| @ -28,8 +41,8 @@ test.describe('Code pane and errors', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -49,11 +62,11 @@ test.describe('Code pane and errors', () => { | |||||||
|     await expect(codePaneButtonHolder).toContainText('notification') |     await expect(codePaneButtonHolder).toContainText('notification') | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test.skip('Opening and closing the code pane will consistently show error diagnostics', async ({ |   test('Opening and closing the code pane will consistently show error diagnostics', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|     editor, |  | ||||||
|   }) => { |   }) => { | ||||||
|  |     await page.goto('http://localhost:3000') | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     // Load the app with the working starter code |     // Load the app with the working starter code | ||||||
| @ -61,8 +74,8 @@ test.describe('Code pane and errors', () => { | |||||||
|       localStorage.setItem('persistCode', code) |       localStorage.setItem('persistCode', code) | ||||||
|     }, bracket) |     }, bracket) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 900 }) |     await page.setViewportSize({ width: 1200, height: 900 }) | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -78,9 +91,8 @@ test.describe('Code pane and errors', () => { | |||||||
|     await expect(codePaneButtonHolder).not.toContainText('notification') |     await expect(codePaneButtonHolder).not.toContainText('notification') | ||||||
|  |  | ||||||
|     // Delete a character to break the KCL |     // Delete a character to break the KCL | ||||||
|     await editor.openPane() |     await u.openKclCodePanel() | ||||||
|     await editor.scrollToText('thickness, bracketLeg1Sketch)') |     await page.getByText('thickness, bracketLeg1Sketch)').click() | ||||||
|     await page.getByText('extrude(thickness, bracketLeg1Sketch)').click() |  | ||||||
|     await page.keyboard.press('Backspace') |     await page.keyboard.press('Backspace') | ||||||
|  |  | ||||||
|     // Ensure that a badge appears on the button |     // Ensure that a badge appears on the button | ||||||
| @ -104,10 +116,7 @@ test.describe('Code pane and errors', () => { | |||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
|  |  | ||||||
|     // Open the code pane |     // Open the code pane | ||||||
|     await editor.openPane() |     await u.openKclCodePanel() | ||||||
|  |  | ||||||
|     // Go to our problematic code again (missing closing paren!) |  | ||||||
|     await editor.scrollToText('extrude(thickness, bracketLeg1Sketch') |  | ||||||
|  |  | ||||||
|     // Ensure that a badge appears on the button |     // Ensure that a badge appears on the button | ||||||
|     await expect(codePaneButtonHolder).toContainText('notification') |     await expect(codePaneButtonHolder).toContainText('notification') | ||||||
| @ -120,16 +129,18 @@ test.describe('Code pane and errors', () => { | |||||||
|     await expect(page.locator('.cm-tooltip').first()).toBeVisible() |     await expect(page.locator('.cm-tooltip').first()).toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test.fixme( |   test('When error is not in view you can click the badge to scroll to it', async ({ | ||||||
|     'When error is not in view you can click the badge to scroll to it', |     page, | ||||||
|     async ({ page, homePage, context }) => { |   }) => { | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     // Load the app with the working starter code |     // Load the app with the working starter code | ||||||
|       await context.addInitScript((code) => { |     await page.addInitScript((code) => { | ||||||
|       localStorage.setItem('persistCode', code) |       localStorage.setItem('persistCode', code) | ||||||
|     }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) |     }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await page.waitForTimeout(1000) |     await page.waitForTimeout(1000) | ||||||
|  |  | ||||||
| @ -153,25 +164,24 @@ test.describe('Code pane and errors', () => { | |||||||
|     await expect( |     await expect( | ||||||
|       page |       page | ||||||
|         .getByText( |         .getByText( | ||||||
|             'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed:  sketch profile must lie entirely on one side of the revolution axis" }]' |           'sketch profile must lie entirely on one side of the revolution axis' | ||||||
|         ) |         ) | ||||||
|         .first() |         .first() | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ |   test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ | ||||||
|     context, |  | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     // Load the app with the working starter code |     // Load the app with the working starter code | ||||||
|     await context.addInitScript((code) => { |     await page.addInitScript((code) => { | ||||||
|       localStorage.setItem('persistCode', code) |       localStorage.setItem('persistCode', code) | ||||||
|     }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) |     }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await page.waitForTimeout(1000) |     await page.waitForTimeout(1000) | ||||||
|  |  | ||||||
| @ -231,9 +241,11 @@ test.describe('Code pane and errors', () => { | |||||||
| test( | test( | ||||||
|   'Opening multiple panes persists when switching projects', |   'Opening multiple panes persists when switching projects', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ context, page }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
|     // Setup multiple projects. |     // Setup multiple projects. | ||||||
|     await context.folderSetupFn(async (dir) => { |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|         const routerTemplateDir = join(dir, 'router-template-slate') |         const routerTemplateDir = join(dir, 'router-template-slate') | ||||||
|         const bracketDir = join(dir, 'bracket') |         const bracketDir = join(dir, 'bracket') | ||||||
|         await Promise.all([ |         await Promise.all([ | ||||||
| @ -250,10 +262,11 @@ test( | |||||||
|             join(bracketDir, 'main.kcl') |             join(bracketDir, 'main.kcl') | ||||||
|           ), |           ), | ||||||
|         ]) |         ]) | ||||||
|  |       }, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await test.step('Opening the bracket project should load', async () => { |     await test.step('Opening the bracket project should load', async () => { | ||||||
|       await expect(page.getByText('bracket')).toBeVisible() |       await expect(page.getByText('bracket')).toBeVisible() | ||||||
| @ -296,21 +309,30 @@ test( | |||||||
|       await expect(page.locator('#variables-pane')).toBeVisible() |       await expect(page.locator('#variables-pane')).toBeVisible() | ||||||
|       await expect(page.locator('#logs-pane')).toBeVisible() |       await expect(page.locator('#logs-pane')).toBeVisible() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| test( | test( | ||||||
|   'external change of file contents are reflected in editor', |   'external change of file contents are reflected in editor', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ context, page }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
|     const PROJECT_DIR_NAME = 'lee-was-here' |     const PROJECT_DIR_NAME = 'lee-was-here' | ||||||
|     const { dir: projectsDir } = await context.folderSetupFn(async (dir) => { |     const { | ||||||
|  |       electronApp, | ||||||
|  |       page, | ||||||
|  |       dir: projectsDir, | ||||||
|  |     } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|         const aProjectDir = join(dir, PROJECT_DIR_NAME) |         const aProjectDir = join(dir, PROJECT_DIR_NAME) | ||||||
|         await fsp.mkdir(aProjectDir, { recursive: true }) |         await fsp.mkdir(aProjectDir, { recursive: true }) | ||||||
|  |       }, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await test.step('Open the project', async () => { |     await test.step('Open the project', async () => { | ||||||
|       await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible() |       await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible() | ||||||
| @ -329,5 +351,7 @@ test( | |||||||
|       ) |       ) | ||||||
|       await u.editorTextMatches(content) |       await u.editorTextMatches(content) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1,12 +1,19 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
|  |  | ||||||
| import { getUtils } from './test-utils' | import { getUtils, setup, tearDown } from './test-utils' | ||||||
| import { KCL_DEFAULT_LENGTH } from 'lib/constants' | import { KCL_DEFAULT_LENGTH } from 'lib/constants' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Command bar tests', () => { | test.describe('Command bar tests', () => { | ||||||
|   test('Extrude from command bar selects extrude line after', async ({ |   test('Extrude from command bar selects extrude line after', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -22,9 +29,9 @@ test.describe('Command bar tests', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -45,8 +52,7 @@ test.describe('Command bar tests', () => { | |||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   // TODO: fix this test after the electron migration |   test('Fillet from command bar', async ({ page }) => { | ||||||
|   test.fixme('Fillet from command bar', async ({ page, homePage }) => { |  | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|         'persistCode', |         'persistCode', | ||||||
| @ -62,8 +68,8 @@ test.describe('Command bar tests', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|     await u.closeDebugPanel() |     await u.closeDebugPanel() | ||||||
| @ -87,10 +93,10 @@ test.describe('Command bar tests', () => { | |||||||
|  |  | ||||||
|   test('Command bar can change a setting, and switch back and forth between arguments', async ({ |   test('Command bar can change a setting, and switch back and forth between arguments', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     const u = await getUtils(page) | ||||||
|     await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) |     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|     const cmdSearchBar = page.getByPlaceholder('Search commands') |     const cmdSearchBar = page.getByPlaceholder('Search commands') | ||||||
| @ -147,7 +153,7 @@ test.describe('Command bar tests', () => { | |||||||
|     // Check that the visibility changed |     // Check that the visibility changed | ||||||
|     await expect(paneSelector).not.toBeVisible() |     await expect(paneSelector).not.toBeVisible() | ||||||
|  |  | ||||||
|     commandOptionInput = page.locator('[id="option-input"]') |     commandOptionInput = page.getByPlaceholder('off') | ||||||
|  |  | ||||||
|     // Test case for https://github.com/KittyCAD/modeling-app/issues/2882 |     // Test case for https://github.com/KittyCAD/modeling-app/issues/2882 | ||||||
|     await commandBarButton.click() |     await commandBarButton.click() | ||||||
| @ -168,10 +174,10 @@ test.describe('Command bar tests', () => { | |||||||
|  |  | ||||||
|   test('Command bar keybinding works from code editor and can change a setting', async ({ |   test('Command bar keybinding works from code editor and can change a setting', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     const u = await getUtils(page) | ||||||
|     await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
| @ -215,7 +221,7 @@ test.describe('Command bar tests', () => { | |||||||
|     await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) |     await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('Can extrude from the command bar', async ({ page, homePage }) => { |   test('Can extrude from the command bar', async ({ page }) => { | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|         'persistCode', |         'persistCode', | ||||||
| @ -231,9 +237,9 @@ test.describe('Command bar tests', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // Make sure the stream is up |     // Make sure the stream is up | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -287,19 +293,26 @@ test.describe('Command bar tests', () => { | |||||||
|     await continueButton.click() |     await continueButton.click() | ||||||
|     await submitButton.click() |     await submitButton.click() | ||||||
|  |  | ||||||
|  |     // Check that the code was updated | ||||||
|     await u.waitForCmdReceive('extrude') |     await u.waitForCmdReceive('extrude') | ||||||
|  |     // Unfortunately this indentation seems to matter for the test | ||||||
|     await expect(page.locator('.cm-content')).toContainText( |     await expect(page.locator('.cm-content')).toHaveText( | ||||||
|       'extrude001 = extrude(distance001, sketch001)' |       `distance = sqrt(20) | ||||||
|  | distance001 = ${KCL_DEFAULT_LENGTH} | ||||||
|  | sketch001 = startSketchOn('XZ') | ||||||
|  |     |> startProfileAt([-6.95, 10.98], %) | ||||||
|  |     |> line([25.1, 0.41], %) | ||||||
|  |     |> line([0.73, -20.93], %) | ||||||
|  |     |> line([-23.44, 0.52], %) | ||||||
|  |     |> close(%) | ||||||
|  | extrude001 = extrude(distance001, sketch001)`.replace(/(\r\n|\n|\r)/gm, '') // remove newlines | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('Can switch between sketch tools via command bar', async ({ |   test('Can switch between sketch tools via command bar', async ({ page }) => { | ||||||
|     page, |     const u = await getUtils(page) | ||||||
|     homePage, |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|   }) => { |     await u.waitForAuthSkipAppStart() | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     const sketchButton = page.getByRole('button', { name: 'Start Sketch' }) |     const sketchButton = page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|     const cmdBarButton = page.getByRole('button', { name: 'Commands' }) |     const cmdBarButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|  | |||||||
| @ -1,16 +1,23 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
| import { getUtils } from './test-utils' | import { getUtils, setup, tearDown } from './test-utils' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
| test.describe('Copilot ghost text', () => { | test.describe('Copilot ghost text', () => { | ||||||
|   // eslint-disable-next-line jest/valid-title |   // eslint-disable-next-line jest/valid-title | ||||||
|   test.skip(true, 'Needs to get covered again') |   test.skip(true, 'Needs to get covered again') | ||||||
|  |  | ||||||
|   test('completes code in empty file', async ({ page, homePage }) => { |   test('completes code in empty file', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -45,13 +52,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|  |  | ||||||
|   test.skip('copilot disabled in sketch mode no select plane', async ({ |   test.skip('copilot disabled in sketch mode no select plane', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -95,13 +101,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|  |  | ||||||
|   test('copilot disabled in sketch mode after selecting plane', async ({ |   test('copilot disabled in sketch mode after selecting plane', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -179,12 +184,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() |     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('ArrowUp in code rejects the suggestion', async ({ page, homePage }) => { |   test('ArrowUp in code rejects the suggestion', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -207,15 +212,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('ArrowDown in code rejects the suggestion', async ({ |   test('ArrowDown in code rejects the suggestion', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -238,15 +240,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('ArrowLeft in code rejects the suggestion', async ({ |   test('ArrowLeft in code rejects the suggestion', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -269,15 +268,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('ArrowRight in code rejects the suggestion', async ({ |   test('ArrowRight in code rejects the suggestion', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -300,12 +296,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('Enter in code scoots it down', async ({ page, homePage }) => { |   test('Enter in code scoots it down', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -330,15 +326,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('Ctrl+shift+z in code rejects the suggestion', async ({ |   test('Ctrl+shift+z in code rejects the suggestion', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -367,13 +360,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|  |  | ||||||
|   test('Ctrl+z in code rejects the suggestion and undos the last code', async ({ |   test('Ctrl+z in code rejects the suggestion and undos the last code', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await page.waitForTimeout(800) |     await page.waitForTimeout(800) | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
| @ -428,17 +420,15 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() |     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||||
|  |  | ||||||
|     // TODO when we make codemirror a widget, we can test this. |     // TODO when we make codemirror a widget, we can test this. | ||||||
|     //await expect(page.locator('.cm-content')).toHaveText(``) }) |     //await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|     test('delete in code rejects the suggestion', async ({ |   test('delete in code rejects the suggestion', async ({ page }) => { | ||||||
|       page, |  | ||||||
|       homePage, |  | ||||||
|     }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -463,15 +453,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|     test('backspace in code rejects the suggestion', async ({ |   test('backspace in code rejects the suggestion', async ({ page }) => { | ||||||
|       page, |  | ||||||
|       homePage, |  | ||||||
|     }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -496,15 +483,12 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|     test('focus outside code pane rejects the suggestion', async ({ |   test('focus outside code pane rejects the suggestion', async ({ page }) => { | ||||||
|       page, |  | ||||||
|       homePage, |  | ||||||
|     }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio |     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
| @ -531,4 +515,3 @@ test.describe('Copilot ghost text', () => { | |||||||
|     await expect(page.locator('.cm-content')).toHaveText(``) |     await expect(page.locator('.cm-content')).toHaveText(``) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
| }) |  | ||||||
|  | |||||||
| @ -1,6 +1,14 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
|  |  | ||||||
| import { getUtils } from './test-utils' | import { getUtils, setup, tearDown } from './test-utils' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| function countNewlines(input: string): number { | function countNewlines(input: string): number { | ||||||
|   let count = 0 |   let count = 0 | ||||||
| @ -16,14 +24,13 @@ test.describe('Debug pane', () => { | |||||||
|   test('Artifact IDs in the artifact graph are stable across code edits', async ({ |   test('Artifact IDs in the artifact graph are stable across code edits', async ({ | ||||||
|     page, |     page, | ||||||
|     context, |     context, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const code = `sketch001 = startSketchOn('XZ') |     const code = `sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt([0, 0], %) |   |> startProfileAt([0, 0], %) | ||||||
| |> line([1, 1], %) | |> line([1, 1], %) | ||||||
| ` | ` | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     const tree = page.getByTestId('debug-feature-tree') |     const tree = page.getByTestId('debug-feature-tree') | ||||||
|     const segment = tree.locator('li', { |     const segment = tree.locator('li', { | ||||||
| @ -32,7 +39,7 @@ test.describe('Debug pane', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Test setup', async () => { |     await test.step('Test setup', async () => { | ||||||
|       await homePage.goToModelingScene() |       await u.waitForAuthSkipAppStart() | ||||||
|       await u.openKclCodePanel() |       await u.openKclCodePanel() | ||||||
|       await u.openDebugPanel() |       await u.openDebugPanel() | ||||||
|       // Set the code in the code editor. |       // Set the code in the code editor. | ||||||
|  | |||||||
| @ -1,31 +1,39 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
| import path from 'path' | import { join } from 'path' | ||||||
| import { | import { | ||||||
|   getUtils, |   getUtils, | ||||||
|  |   setupElectron, | ||||||
|  |   tearDown, | ||||||
|   executorInputPath, |   executorInputPath, | ||||||
|   getPlaywrightDownloadDir, |  | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test( | test( | ||||||
|   'export works on the first try', |   'export works on the first try', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ page, context }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
|     await context.folderSetupFn(async (dir) => { |     const { electronApp, page } = await setupElectron({ | ||||||
|       const bracketDir = path.join(dir, 'bracket') |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|  |         const bracketDir = join(dir, 'bracket') | ||||||
|         await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) |         await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) | ||||||
|         await Promise.all([ |         await Promise.all([ | ||||||
|           fsp.copyFile( |           fsp.copyFile( | ||||||
|             executorInputPath('router-template-slate.kcl'), |             executorInputPath('router-template-slate.kcl'), | ||||||
|           path.join(bracketDir, 'other.kcl') |             join(bracketDir, 'other.kcl') | ||||||
|           ), |           ), | ||||||
|           fsp.copyFile( |           fsp.copyFile( | ||||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), |             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|           path.join(bracketDir, 'main.kcl') |             join(bracketDir, 'main.kcl') | ||||||
|           ), |           ), | ||||||
|         ]) |         ]) | ||||||
|  |       }, | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     page.on('console', console.log) |     page.on('console', console.log) | ||||||
|  |  | ||||||
| @ -85,16 +93,12 @@ test( | |||||||
|       await expect(successToastMessage).toBeVisible() |       await expect(successToastMessage).toBeVisible() | ||||||
|       await expect(exportingToastMessage).not.toBeVisible() |       await expect(exportingToastMessage).not.toBeVisible() | ||||||
|  |  | ||||||
|       const firstFileFullPath = path.resolve( |  | ||||||
|         getPlaywrightDownloadDir(page), |  | ||||||
|         exportFileName |  | ||||||
|       ) |  | ||||||
|       await test.step('Check the export size', async () => { |       await test.step('Check the export size', async () => { | ||||||
|         await expect |         await expect | ||||||
|           .poll( |           .poll( | ||||||
|             async () => { |             async () => { | ||||||
|               try { |               try { | ||||||
|                 const outputGltf = await fsp.readFile(firstFileFullPath) |                 const outputGltf = await fsp.readFile(exportFileName) | ||||||
|                 return outputGltf.byteLength |                 return outputGltf.byteLength | ||||||
|               } catch (e) { |               } catch (e) { | ||||||
|                 return 0 |                 return 0 | ||||||
| @ -103,6 +107,9 @@ test( | |||||||
|             { timeout: 15_000 } |             { timeout: 15_000 } | ||||||
|           ) |           ) | ||||||
|           .toBeGreaterThan(300_000) |           .toBeGreaterThan(300_000) | ||||||
|  |  | ||||||
|  |         // clean up exported file | ||||||
|  |         await fsp.rm(exportFileName) | ||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
| @ -163,16 +170,12 @@ test( | |||||||
|           expect(exportingToastMessage).not.toBeVisible(), |           expect(exportingToastMessage).not.toBeVisible(), | ||||||
|         ])) |         ])) | ||||||
|  |  | ||||||
|       const secondFileFullPath = path.resolve( |  | ||||||
|         getPlaywrightDownloadDir(page), |  | ||||||
|         exportFileName |  | ||||||
|       ) |  | ||||||
|       await test.step('Check the export size', async () => { |       await test.step('Check the export size', async () => { | ||||||
|         await expect |         await expect | ||||||
|           .poll( |           .poll( | ||||||
|             async () => { |             async () => { | ||||||
|               try { |               try { | ||||||
|                 const outputGltf = await fsp.readFile(secondFileFullPath) |                 const outputGltf = await fsp.readFile(exportFileName) | ||||||
|                 return outputGltf.byteLength |                 return outputGltf.byteLength | ||||||
|               } catch (e) { |               } catch (e) { | ||||||
|                 return 0 |                 return 0 | ||||||
| @ -181,7 +184,13 @@ test( | |||||||
|             { timeout: 15_000 } |             { timeout: 15_000 } | ||||||
|           ) |           ) | ||||||
|           .toBeGreaterThan(100_000) |           .toBeGreaterThan(100_000) | ||||||
|  |  | ||||||
|  |         // clean up exported file | ||||||
|  |         await fsp.rm(exportFileName) | ||||||
|       }) |       }) | ||||||
|  |       await electronApp.close() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
| import { uuidv4 } from 'lib/utils' | import { uuidv4 } from 'lib/utils' | ||||||
| import { | import { | ||||||
| @ -6,16 +6,26 @@ import { | |||||||
|   darkModePlaneColorXZ, |   darkModePlaneColorXZ, | ||||||
|   executorInputPath, |   executorInputPath, | ||||||
|   getUtils, |   getUtils, | ||||||
|  |   setup, | ||||||
|  |   setupElectron, | ||||||
|  |   tearDown, | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
|  |  | ||||||
| import { join } from 'path' | import { join } from 'path' | ||||||
|  |  | ||||||
| test.describe('Editor tests', () => { | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|   test('can comment out code with ctrl+/', async ({ page, homePage }) => { |   await setup(context, page, testInfo) | ||||||
|     const u = await getUtils(page) | }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.describe('Editor tests', () => { | ||||||
|  |   test('can comment out code with ctrl+/', async ({ page }) => { | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
| @ -54,116 +64,13 @@ test.describe('Editor tests', () => { | |||||||
|     |> close(%)`) |     |> close(%)`) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('ensure we use the cache, and do not re-execute', async ({ |  | ||||||
|     homePage, |  | ||||||
|     page, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |  | ||||||
|     await page.keyboard.type(`sketch001 = startSketchOn('XY') |  | ||||||
|   |> startProfileAt([-10, -10], %) |  | ||||||
|   |> line([20, 0], %) |  | ||||||
|   |> line([0, 20], %) |  | ||||||
|   |> line([-20, 0], %) |  | ||||||
|   |> close(%)`) |  | ||||||
|  |  | ||||||
|     // Ensure we execute the first time. |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-receive-command-type="scene_clear_all"]') |  | ||||||
|     ).toHaveCount(1) |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-message-type="execution-done"]') |  | ||||||
|     ).toHaveCount(2) |  | ||||||
|  |  | ||||||
|     // Add whitespace to the end of the code. |  | ||||||
|     await u.codeLocator.click() |  | ||||||
|     await page.keyboard.press('ArrowUp') |  | ||||||
|     await page.keyboard.press('ArrowUp') |  | ||||||
|     await page.keyboard.press('ArrowUp') |  | ||||||
|     await page.keyboard.press('ArrowUp') |  | ||||||
|     await page.keyboard.press('Home') |  | ||||||
|     await page.keyboard.type('    ') |  | ||||||
|     await page.keyboard.press('Enter') |  | ||||||
|     await page.keyboard.type('    ') |  | ||||||
|  |  | ||||||
|     // Ensure we don't execute the second time. |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     // Make sure we didn't clear the scene. |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-message-type="execution-done"]') |  | ||||||
|     ).toHaveCount(3) |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-receive-command-type="scene_clear_all"]') |  | ||||||
|     ).toHaveCount(1) |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   test('ensure we use the cache, and do not clear on append', async ({ |  | ||||||
|     homePage, |  | ||||||
|     page, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     await u.codeLocator.click() |  | ||||||
|     await page.keyboard.type(`sketch001 = startSketchOn('XY') |  | ||||||
|   |> startProfileAt([-10, -10], %) |  | ||||||
|   |> line([20, 0], %) |  | ||||||
|   |> line([0, 20], %) |  | ||||||
|   |> line([-20, 0], %) |  | ||||||
|   |> close(%)`) |  | ||||||
|  |  | ||||||
|     // Ensure we execute the first time. |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-receive-command-type="scene_clear_all"]') |  | ||||||
|     ).toHaveCount(1) |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-message-type="execution-done"]') |  | ||||||
|     ).toHaveCount(2) |  | ||||||
|  |  | ||||||
|     // Add whitespace to the end of the code. |  | ||||||
|     await u.codeLocator.click() |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await page.keyboard.press('End') |  | ||||||
|     await page.keyboard.press('Enter') |  | ||||||
|     await page.keyboard.press('Enter') |  | ||||||
|     await page.keyboard.type('const x = 1') |  | ||||||
|     await page.keyboard.press('Enter') |  | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-message-type="execution-done"]') |  | ||||||
|     ).toHaveCount(3) |  | ||||||
|     await expect( |  | ||||||
|       page.locator('[data-receive-command-type="scene_clear_all"]') |  | ||||||
|     ).toHaveCount(1) |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   test('if you click the format button it formats your code', async ({ |   test('if you click the format button it formats your code', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
| @ -189,12 +96,11 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|   test('if you click the format button it formats your code and executes so lints are still there', async ({ |   test('if you click the format button it formats your code and executes so lints are still there', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
| @ -245,7 +151,9 @@ test.describe('Editor tests', () => { | |||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('fold gutters work', async ({ page, homePage }) => { |   test('fold gutters work', async ({ page }) => { | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     const fullCode = `sketch001 = startSketchOn('XY') |     const fullCode = `sketch001 = startSketchOn('XY') | ||||||
|      |> startProfileAt([-10, -10], %) |      |> startProfileAt([-10, -10], %) | ||||||
|      |> line([20, 0], %) |      |> line([20, 0], %) | ||||||
| @ -263,9 +171,9 @@ test.describe('Editor tests', () => { | |||||||
|      |> close(%)` |      |> close(%)` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // TODO: Jess needs to fix this but you have to mod the code to get them to show |     // TODO: Jess needs to fix this but you have to mod the code to get them to show | ||||||
|     // up, its an annoying codemirror thing. |     // up, its an annoying codemirror thing. | ||||||
| @ -316,10 +224,7 @@ test.describe('Editor tests', () => { | |||||||
|     await expect(foldGutterFoldLine).not.toBeVisible() |     await expect(foldGutterFoldLine).not.toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('hover over functions shows function description', async ({ |   test('hover over functions shows function description', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -332,9 +237,9 @@ test.describe('Editor tests', () => { | |||||||
|   |> close(%)` |   |> close(%)` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
| @ -363,7 +268,6 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|   test('if you use the format keyboard binding it formats your code', async ({ |   test('if you use the format keyboard binding it formats your code', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
| @ -378,9 +282,9 @@ test.describe('Editor tests', () => { | |||||||
|       ) |       ) | ||||||
|       localStorage.setItem('disableAxis', 'true') |       localStorage.setItem('disableAxis', 'true') | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
| @ -406,7 +310,6 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|   test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({ |   test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
| @ -421,9 +324,9 @@ test.describe('Editor tests', () => { | |||||||
|       ) |       ) | ||||||
|       localStorage.setItem('disableAxis', 'true') |       localStorage.setItem('disableAxis', 'true') | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -466,14 +369,11 @@ test.describe('Editor tests', () => { | |||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('if you write kcl with lint errors you get lints', async ({ |   test('if you write kcl with lint errors you get lints', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() | ||||||
| @ -509,10 +409,7 @@ test.describe('Editor tests', () => { | |||||||
|     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('if you fixup kcl errors you clear lints', async ({ |   test('if you fixup kcl errors you clear lints', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -526,9 +423,9 @@ test.describe('Editor tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
| @ -550,27 +447,22 @@ test.describe('Editor tests', () => { | |||||||
|     ).not.toBeVisible() |     ).not.toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('if you write invalid kcl you get inlined errors', async ({ |   test('if you write invalid kcl you get inlined errors', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // check no error to begin with |     // check no error to begin with | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
|  |  | ||||||
|     /* add the following code to the editor (~ error is not a valid line) |     /* add the following code to the editor ($ error is not a valid line) | ||||||
|       * the old check here used $ but this is for tags so it changed meaning. |       $ error | ||||||
|       * hopefully ~ doesn't change meaning |       topAng = 30 | ||||||
|     ~ error |       bottomAng = 25 | ||||||
|     const topAng = 30 |  | ||||||
|     const bottomAng = 25 |  | ||||||
|      */ |      */ | ||||||
|     await u.codeLocator.click() |     await u.codeLocator.click() | ||||||
|     await page.keyboard.type('~ error') |     await page.keyboard.type('$ error') | ||||||
|  |  | ||||||
|     // press arrows to clear autocomplete |     // press arrows to clear autocomplete | ||||||
|     await page.keyboard.press('ArrowLeft') |     await page.keyboard.press('ArrowLeft') | ||||||
| @ -582,17 +474,17 @@ test.describe('Editor tests', () => { | |||||||
|     await page.keyboard.type('bottomAng = 25') |     await page.keyboard.type('bottomAng = 25') | ||||||
|     await page.keyboard.press('Enter') |     await page.keyboard.press('Enter') | ||||||
|  |  | ||||||
|     // error in guter |     // error in gutter | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||||
|  |  | ||||||
|     // error text on hover |     // error text on hover | ||||||
|     await page.hover('.cm-lint-marker-error') |     await page.hover('.cm-lint-marker-error') | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByText("found unknown token '~'").first() |       page.getByText('Tag names must not be empty').first() | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|  |  | ||||||
|     // select the line that's causing the error and delete it |     // select the line that's causing the error and delete it | ||||||
|     await page.getByText('~ error').click() |     await page.getByText('$ error').click() | ||||||
|     await page.keyboard.press('End') |     await page.keyboard.press('End') | ||||||
|     await page.keyboard.down('Shift') |     await page.keyboard.down('Shift') | ||||||
|     await page.keyboard.press('Home') |     await page.keyboard.press('Home') | ||||||
| @ -628,9 +520,10 @@ test.describe('Editor tests', () => { | |||||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test.fixme( |   // TODO currently multiple source ranges are not supported | ||||||
|     'error with 2 source ranges gets 2 diagnostics', |   test.skip('error with 2 source ranges gets 2 diagnostics', async ({ | ||||||
|     async ({ page, homePage }) => { |     page, | ||||||
|  |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -652,11 +545,9 @@ test.describe('Editor tests', () => { | |||||||
|   ` |   ` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|       await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|       await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|       await u.waitForPageLoad() |  | ||||||
|       await page.waitForTimeout(1000) |  | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -682,7 +573,7 @@ test.describe('Editor tests', () => { | |||||||
|     await page.keyboard.press('ArrowDown') |     await page.keyboard.press('ArrowDown') | ||||||
|     await page.keyboard.press('Enter') |     await page.keyboard.press('Enter') | ||||||
|     await page.keyboard.type(`extrusion = startSketchOn('XY') |     await page.keyboard.type(`extrusion = startSketchOn('XY') | ||||||
|   |> circle({ center: [0, 0], radius: dia/2 }, %) |     |> circle({ center = [0, 0], radius = dia/2 }, %) | ||||||
|   |> hole(squareHole(length, width, height), %) |   |> hole(squareHole(length, width, height), %) | ||||||
|   |> extrude(height, %)`) |   |> extrude(height, %)`) | ||||||
|  |  | ||||||
| @ -695,14 +586,12 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|     // Make sure there are two diagnostics |     // Make sure there are two diagnostics | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2) |     await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2) | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
|   test('if your kcl gets an error from the engine it is inlined', async ({ |   test('if your kcl gets an error from the engine it is inlined', async ({ | ||||||
|     context, |  | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     await context.addInitScript(async () => { |     const u = await getUtils(page) | ||||||
|  |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|         'persistCode', |         'persistCode', | ||||||
|         `box = startSketchOn('XY') |         `box = startSketchOn('XY') | ||||||
| @ -720,16 +609,17 @@ test.describe('Editor tests', () => { | |||||||
|   |> line([0, -10], %) |   |> line([0, -10], %) | ||||||
|   |> close(%) |   |> close(%) | ||||||
|   |> revolve({ |   |> revolve({ | ||||||
|     axis: revolveAxis, |   axis = revolveAxis, | ||||||
|     angle: 90 |   angle = 90 | ||||||
|   }, %) |   }, %) | ||||||
|       ` |       ` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await page.goto('/') | ||||||
|  |     await u.waitForPageLoad() | ||||||
|  |  | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||||
|  |  | ||||||
| @ -740,15 +630,12 @@ test.describe('Editor tests', () => { | |||||||
|     await expect(page.getByText(searchText)).toBeVisible() |     await expect(page.getByText(searchText)).toBeVisible() | ||||||
|   }) |   }) | ||||||
|   test.describe('Autocomplete works', () => { |   test.describe('Autocomplete works', () => { | ||||||
|     test('with enter/click to accept the completion', async ({ |     test('with enter/click to accept the completion', async ({ page }) => { | ||||||
|       page, |  | ||||||
|       homePage, |  | ||||||
|     }) => { |  | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio |       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       await homePage.goToModelingScene() |       await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|       // tests clicking on an option, selection the first option |       // tests clicking on an option, selection the first option | ||||||
|       // and arrowing down to an option |       // and arrowing down to an option | ||||||
| @ -817,12 +704,12 @@ test.describe('Editor tests', () => { | |||||||
|       await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) |       await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     test('with tab to accept the completion', async ({ page, homePage }) => { |     test('with tab to accept the completion', async ({ page }) => { | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio |       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       await homePage.goToModelingScene() |       await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|       // this test might be brittle as we add and remove functions |       // this test might be brittle as we add and remove functions | ||||||
|       // but should also be easy to update. |       // but should also be easy to update. | ||||||
| @ -888,13 +775,9 @@ test.describe('Editor tests', () => { | |||||||
|     |> xLine(5, %) // lin`) |     |> xLine(5, %) // lin`) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|   test('Can undo a click and point extrude with ctrl+z', async ({ |   test('Can undo a click and point extrude with ctrl+z', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     context, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await context.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|         'persistCode', |         'persistCode', | ||||||
|         `sketch001 = startSketchOn('XZ') |         `sketch001 = startSketchOn('XZ') | ||||||
| @ -905,9 +788,9 @@ test.describe('Editor tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|     ).not.toBeDisabled() |     ).not.toBeDisabled() | ||||||
| @ -966,10 +849,7 @@ test.describe('Editor tests', () => { | |||||||
|     |> close(%)`) |     |> close(%)`) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('Can undo a sketch modification with ctrl+z', async ({ |   test('Can undo a sketch modification with ctrl+z', async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -983,9 +863,9 @@ test.describe('Editor tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|     ).not.toBeDisabled() |     ).not.toBeDisabled() | ||||||
| @ -1012,7 +892,7 @@ test.describe('Editor tests', () => { | |||||||
|     }) |     }) | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|     const startPX = [1200 / 2, 500 / 2] |     const startPX = [665, 397] | ||||||
|  |  | ||||||
|     const dragPX = 40 |     const dragPX = 40 | ||||||
|  |  | ||||||
| @ -1026,9 +906,9 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|     await expect(page.getByTestId('segment-overlay')).toHaveCount(2) |     await expect(page.getByTestId('segment-overlay')).toHaveCount(2) | ||||||
|  |  | ||||||
|     // drag startProfileAt handle |     // drag startProfieAt handle | ||||||
|     await page.dragAndDrop('#stream', '#stream', { |     await page.dragAndDrop('#stream', '#stream', { | ||||||
|       sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 }, |       sourcePosition: { x: startPX[0], y: startPX[1] }, | ||||||
|       targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX }, |       targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX }, | ||||||
|     }) |     }) | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
| @ -1066,8 +946,8 @@ test.describe('Editor tests', () => { | |||||||
|     // expect the code to have changed |     // expect the code to have changed | ||||||
|     await expect(page.locator('.cm-content')) |     await expect(page.locator('.cm-content')) | ||||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') |       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt([2.71, -2.71], %) |   |> startProfileAt([7.12, -12.68], %) | ||||||
|     |> line([15.4, -2.78], %) |   |> line([15.39, -2.78], %) | ||||||
|   |> tangentialArcTo([27.6, -3.05], %) |   |> tangentialArcTo([27.6, -3.05], %) | ||||||
|   |> close(%) |   |> close(%) | ||||||
|   |> extrude(5, %) |   |> extrude(5, %) | ||||||
| @ -1080,8 +960,8 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|     await expect(page.locator('.cm-content')) |     await expect(page.locator('.cm-content')) | ||||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') |       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt([2.71, -2.71], %) |   |> startProfileAt([7.12, -12.68], %) | ||||||
|     |> line([15.4, -2.78], %) |   |> line([15.39, -2.78], %) | ||||||
|   |> tangentialArcTo([24.95, -0.38], %) |   |> tangentialArcTo([24.95, -0.38], %) | ||||||
|   |> close(%) |   |> close(%) | ||||||
|   |> extrude(5, %)`) |   |> extrude(5, %)`) | ||||||
| @ -1093,7 +973,7 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|     await expect(page.locator('.cm-content')) |     await expect(page.locator('.cm-content')) | ||||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') |       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt([2.71, -2.71], %) |   |> startProfileAt([7.12, -12.68], %) | ||||||
|   |> line([12.73, -0.09], %) |   |> line([12.73, -0.09], %) | ||||||
|   |> tangentialArcTo([24.95, -0.38], %) |   |> tangentialArcTo([24.95, -0.38], %) | ||||||
|   |> close(%) |   |> close(%) | ||||||
| @ -1118,8 +998,10 @@ test.describe('Editor tests', () => { | |||||||
|   test.fixme( |   test.fixme( | ||||||
|     `Can use the import stdlib function on a local OBJ file`, |     `Can use the import stdlib function on a local OBJ file`, | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ page, context }, testInfo) => { |     async ({ browserName }, testInfo) => { | ||||||
|       await context.folderSetupFn(async (dir) => { |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|           const bracketDir = join(dir, 'cube') |           const bracketDir = join(dir, 'cube') | ||||||
|           await fsp.mkdir(bracketDir, { recursive: true }) |           await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|           await fsp.copyFile( |           await fsp.copyFile( | ||||||
| @ -1127,10 +1009,10 @@ test.describe('Editor tests', () => { | |||||||
|             join(bracketDir, 'cube.obj') |             join(bracketDir, 'cube.obj') | ||||||
|           ) |           ) | ||||||
|           await fsp.writeFile(join(bracketDir, 'main.kcl'), '') |           await fsp.writeFile(join(bracketDir, 'main.kcl'), '') | ||||||
|  |         }, | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       const viewportSize = { width: 1200, height: 500 } |       const viewportSize = { width: 1200, height: 500 } | ||||||
|       await page.setBodyDimensions(viewportSize) |       await page.setViewportSize(viewportSize) | ||||||
|  |  | ||||||
|       // Locators and constants |       // Locators and constants | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
| @ -1188,6 +1070,8 @@ test.describe('Editor tests', () => { | |||||||
|           }) |           }) | ||||||
|           .toBeGreaterThan(15) |           .toBeGreaterThan(15) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -1,127 +0,0 @@ | |||||||
| import { test, expect } from './zoo-test' |  | ||||||
| import * as fsp from 'fs/promises' |  | ||||||
| import { join } from 'path' |  | ||||||
|  |  | ||||||
| const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) { |  | ||||||
|   return 5 * x |  | ||||||
| } |  | ||||||
| export fn triangle() { |  | ||||||
|   return startSketchOn('XZ') |  | ||||||
|     |> startProfileAt([0, 0], %) |  | ||||||
|     |> xLine(10, %) |  | ||||||
|     |> line([-10, -5], %) |  | ||||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) |  | ||||||
|     |> close(%) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| length001 = timesFive(1) * 5 |  | ||||||
| sketch001 = startSketchOn('XZ') |  | ||||||
|   |> startProfileAt([20, 10], %) |  | ||||||
|   |> line([10, 10], %) |  | ||||||
|   |> angledLine([-45, length001], %) |  | ||||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) |  | ||||||
|   |> close(%) |  | ||||||
| revolve001 = revolve({ axis = "X" }, sketch001) |  | ||||||
| triangle() |  | ||||||
|   |> extrude(30, %) |  | ||||||
| plane001 = offsetPlane('XY', 10) |  | ||||||
| sketch002 = startSketchOn(plane001) |  | ||||||
|   |> startProfileAt([-20, 0], %) |  | ||||||
|   |> line([5, -15], %) |  | ||||||
|   |> xLine(-10, %) |  | ||||||
|   |> lineTo([-40, 0], %) |  | ||||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) |  | ||||||
|   |> close(%) |  | ||||||
| extrude001 = extrude(10, sketch002) |  | ||||||
| ` |  | ||||||
|  |  | ||||||
| test.describe('Feature Tree pane', () => { |  | ||||||
|   test( |  | ||||||
|     'User can go to definition and go to function definition', |  | ||||||
|     { tag: '@electron' }, |  | ||||||
|     async ({ context, homePage, scene, editor, toolbar }) => { |  | ||||||
|       await context.folderSetupFn(async (dir) => { |  | ||||||
|         const bracketDir = join(dir, 'test-sample') |  | ||||||
|         await fsp.mkdir(bracketDir, { recursive: true }) |  | ||||||
|         await fsp.writeFile( |  | ||||||
|           join(bracketDir, 'main.kcl'), |  | ||||||
|           FEATURE_TREE_EXAMPLE_CODE, |  | ||||||
|           'utf-8' |  | ||||||
|         ) |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|       await test.step('setup test', async () => { |  | ||||||
|         await homePage.expectState({ |  | ||||||
|           projectCards: [ |  | ||||||
|             { |  | ||||||
|               title: 'test-sample', |  | ||||||
|               fileCount: 1, |  | ||||||
|             }, |  | ||||||
|           ], |  | ||||||
|           sortBy: 'last-modified-desc', |  | ||||||
|         }) |  | ||||||
|         await homePage.openProject('test-sample') |  | ||||||
|         await scene.waitForExecutionDone() |  | ||||||
|         await editor.closePane() |  | ||||||
|         await toolbar.openFeatureTreePane() |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|       async function testViewSource({ |  | ||||||
|         operationName, |  | ||||||
|         operationIndex, |  | ||||||
|         expectedActiveLine, |  | ||||||
|       }: { |  | ||||||
|         operationName: string |  | ||||||
|         operationIndex: number |  | ||||||
|         expectedActiveLine: string |  | ||||||
|       }) { |  | ||||||
|         await test.step(`Go to definition of the ${operationName}`, async () => { |  | ||||||
|           await toolbar.viewSourceOnOperation(operationName, operationIndex) |  | ||||||
|           await editor.expectState({ |  | ||||||
|             highlightedCode: '', |  | ||||||
|             diagnostics: [], |  | ||||||
|             activeLines: [expectedActiveLine], |  | ||||||
|           }) |  | ||||||
|           await expect( |  | ||||||
|             editor.activeLine.first(), |  | ||||||
|             `${operationName} code should be scrolled into view` |  | ||||||
|           ).toBeVisible() |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       await testViewSource({ |  | ||||||
|         operationName: 'Offset Plane', |  | ||||||
|         operationIndex: 0, |  | ||||||
|         expectedActiveLine: "plane001 = offsetPlane('XY', 10)", |  | ||||||
|       }) |  | ||||||
|       await testViewSource({ |  | ||||||
|         operationName: 'Extrude', |  | ||||||
|         operationIndex: 1, |  | ||||||
|         expectedActiveLine: 'extrude001 = extrude(10, sketch002)', |  | ||||||
|       }) |  | ||||||
|       await testViewSource({ |  | ||||||
|         operationName: 'Revolve', |  | ||||||
|         operationIndex: 0, |  | ||||||
|         expectedActiveLine: 'revolve001 = revolve({ axis = "X" }, sketch001)', |  | ||||||
|       }) |  | ||||||
|       await testViewSource({ |  | ||||||
|         operationName: 'Triangle', |  | ||||||
|         operationIndex: 0, |  | ||||||
|         expectedActiveLine: 'triangle()', |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|       await test.step('Go to definition on the triangle function', async () => { |  | ||||||
|         await toolbar.goToDefinitionOnOperation('Triangle', 0) |  | ||||||
|         await editor.expectState({ |  | ||||||
|           highlightedCode: '', |  | ||||||
|           diagnostics: [], |  | ||||||
|           activeLines: ['export fn triangle() {'], |  | ||||||
|         }) |  | ||||||
|         await expect( |  | ||||||
|           editor.activeLine.first(), |  | ||||||
|           'Triangle function definition should be scrolled into view' |  | ||||||
|         ).toBeVisible() |  | ||||||
|       }) |  | ||||||
|     } |  | ||||||
|   ) |  | ||||||
| }) |  | ||||||
| @ -1,4 +1,4 @@ | |||||||
| import type { Page, Locator } from '@playwright/test' | import type { Page } from '@playwright/test' | ||||||
| import { expect } from '@playwright/test' | import { expect } from '@playwright/test' | ||||||
|  |  | ||||||
| type CmdBarSerialised = | type CmdBarSerialised = | ||||||
| @ -26,11 +26,9 @@ type CmdBarSerialised = | |||||||
|  |  | ||||||
| export class CmdBarFixture { | export class CmdBarFixture { | ||||||
|   public page: Page |   public page: Page | ||||||
|   cmdBarOpenBtn!: Locator |  | ||||||
|  |  | ||||||
|   constructor(page: Page) { |   constructor(page: Page) { | ||||||
|     this.page = page |     this.page = page | ||||||
|     this.cmdBarOpenBtn = page.getByTestId('command-bar-open-button') |  | ||||||
|   } |   } | ||||||
|   reConstruct = (page: Page) => { |   reConstruct = (page: Page) => { | ||||||
|     this.page = page |     this.page = page | ||||||
| @ -118,21 +116,4 @@ export class CmdBarFixture { | |||||||
|       await this.page.keyboard.press('Enter') |       await this.page.keyboard.press('Enter') | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   openCmdBar = async (selectCmd?: 'promptToEdit') => { |  | ||||||
|     // TODO why does this button not work in electron tests? |  | ||||||
|     // await this.cmdBarOpenBtn.click() |  | ||||||
|     await this.page.keyboard.down('ControlOrMeta') |  | ||||||
|     await this.page.keyboard.press('KeyK') |  | ||||||
|     await this.page.keyboard.up('ControlOrMeta') |  | ||||||
|     await expect(this.page.getByPlaceholder('Search commands')).toBeVisible() |  | ||||||
|     if (selectCmd === 'promptToEdit') { |  | ||||||
|       const promptEditCommand = this.page.getByText( |  | ||||||
|         'Use Zoo AI to edit your kcl' |  | ||||||
|       ) |  | ||||||
|       await expect(promptEditCommand.first()).toBeVisible() |  | ||||||
|       await promptEditCommand.first().scrollIntoViewIfNeeded() |  | ||||||
|       await promptEditCommand.first().click() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ export class EditorFixture { | |||||||
|   private diagnosticsTooltip!: Locator |   private diagnosticsTooltip!: Locator | ||||||
|   private diagnosticsGutterIcon!: Locator |   private diagnosticsGutterIcon!: Locator | ||||||
|   private codeContent!: Locator |   private codeContent!: Locator | ||||||
|   public activeLine!: Locator |   private activeLine!: Locator | ||||||
|  |  | ||||||
|   constructor(page: Page) { |   constructor(page: Page) { | ||||||
|     this.page = page |     this.page = page | ||||||
| @ -29,7 +29,7 @@ export class EditorFixture { | |||||||
|   reConstruct = (page: Page) => { |   reConstruct = (page: Page) => { | ||||||
|     this.page = page |     this.page = page | ||||||
|  |  | ||||||
|     this.codeContent = page.locator('.cm-content[data-language="kcl"]') |     this.codeContent = page.locator('.cm-content') | ||||||
|     this.diagnosticsTooltip = page.locator('.cm-tooltip-lint') |     this.diagnosticsTooltip = page.locator('.cm-tooltip-lint') | ||||||
|     this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error') |     this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error') | ||||||
|     this.activeLine = this.page.locator('.cm-activeLine') |     this.activeLine = this.page.locator('.cm-activeLine') | ||||||
| @ -54,13 +54,13 @@ export class EditorFixture { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       if (!shouldNormalise) { |       if (!shouldNormalise) { | ||||||
|         const expectStart = expect.poll(() => this.codeContent.textContent()) |         const expectStart = expect(this.codeContent) | ||||||
|         if (not) { |         if (not) { | ||||||
|           const result = await expectStart.not.toContain(code) |           const result = await expectStart.not.toContainText(code, { timeout }) | ||||||
|           await resetPane() |           await resetPane() | ||||||
|           return result |           return result | ||||||
|         } |         } | ||||||
|         const result = await expectStart.toContain(code) |         const result = await expectStart.toContainText(code, { timeout }) | ||||||
|         await resetPane() |         await resetPane() | ||||||
|         return result |         return result | ||||||
|       } |       } | ||||||
| @ -147,28 +147,4 @@ export class EditorFixture { | |||||||
|   openPane() { |   openPane() { | ||||||
|     return openPane(this.page, this.paneButtonTestId) |     return openPane(this.page, this.paneButtonTestId) | ||||||
|   } |   } | ||||||
|   scrollToText(text: string, placeCursor?: boolean) { |  | ||||||
|     return this.page.evaluate( |  | ||||||
|       (args: { text: string; placeCursor?: boolean }) => { |  | ||||||
|         // error TS2339: Property 'docView' does not exist on type 'EditorView'. |  | ||||||
|         // Except it does so :shrug: |  | ||||||
|         // @ts-ignore |  | ||||||
|         let index = window.editorManager._editorView?.docView.view.state.doc |  | ||||||
|           .toString() |  | ||||||
|           .indexOf(args.text) |  | ||||||
|         window.editorManager._editorView?.focus() |  | ||||||
|         window.editorManager._editorView?.dispatch({ |  | ||||||
|           selection: window.EditorSelection.create([ |  | ||||||
|             window.EditorSelection.cursor(index), |  | ||||||
|           ]), |  | ||||||
|           effects: [ |  | ||||||
|             window.EditorView.scrollIntoView( |  | ||||||
|               window.EditorSelection.range(index, index + 1) |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         }) |  | ||||||
|       }, |  | ||||||
|       { text, placeCursor } |  | ||||||
|     ) |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| import type { | import type { | ||||||
|   BrowserContext, |   BrowserContext, | ||||||
|   ElectronApplication, |   ElectronApplication, | ||||||
|   TestInfo, |  | ||||||
|   Page, |   Page, | ||||||
|  |   TestInfo, | ||||||
| } from '@playwright/test' | } from '@playwright/test' | ||||||
|  | import { test as base } from '@playwright/test' | ||||||
| import { getUtils, setup, setupElectron } from '../test-utils' | import { getUtils, setup, setupElectron, tearDown } from '../test-utils' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
| import { join } from 'path' | import { join } from 'path' | ||||||
| import { CmdBarFixture } from './cmdBarFixture' | import { CmdBarFixture } from './cmdBarFixture' | ||||||
| @ -20,13 +20,11 @@ export class AuthenticatedApp { | |||||||
|   public readonly page: Page |   public readonly page: Page | ||||||
|   public readonly context: BrowserContext |   public readonly context: BrowserContext | ||||||
|   public readonly testInfo: TestInfo |   public readonly testInfo: TestInfo | ||||||
|   public readonly viewPortSize = { width: 1200, height: 500 } |   public readonly viewPortSize = { width: 1000, height: 500 } | ||||||
|   public electronApp: undefined | ElectronApplication |  | ||||||
|   public dir: string = '' |  | ||||||
|  |  | ||||||
|   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { |   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { | ||||||
|     this.context = context |  | ||||||
|     this.page = page |     this.page = page | ||||||
|  |     this.context = context | ||||||
|     this.testInfo = testInfo |     this.testInfo = testInfo | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -51,7 +49,9 @@ export class AuthenticatedApp { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface Fixtures { | interface Fixtures { | ||||||
|  |   app: AuthenticatedApp | ||||||
|  |   tronApp: AuthenticatedTronApp | ||||||
|   cmdBar: CmdBarFixture |   cmdBar: CmdBarFixture | ||||||
|   editor: EditorFixture |   editor: EditorFixture | ||||||
|   toolbar: ToolbarFixture |   toolbar: ToolbarFixture | ||||||
| @ -61,11 +61,9 @@ export interface Fixtures { | |||||||
| export class AuthenticatedTronApp { | export class AuthenticatedTronApp { | ||||||
|   public readonly _page: Page |   public readonly _page: Page | ||||||
|   public page: Page |   public page: Page | ||||||
|   public context: BrowserContext |   public readonly context: BrowserContext | ||||||
|   public readonly testInfo: TestInfo |   public readonly testInfo: TestInfo | ||||||
|   public electronApp: ElectronApplication | undefined |   public electronApp?: ElectronApplication | ||||||
|   public readonly viewPortSize = { width: 1200, height: 500 } |  | ||||||
|   public dir: string = '' |  | ||||||
|  |  | ||||||
|   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { |   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { | ||||||
|     this._page = page |     this._page = page | ||||||
| @ -81,22 +79,15 @@ export class AuthenticatedTronApp { | |||||||
|       appSettings?: Partial<SaveSettingsPayload> |       appSettings?: Partial<SaveSettingsPayload> | ||||||
|     } = { fixtures: {} } |     } = { fixtures: {} } | ||||||
|   ) { |   ) { | ||||||
|     const { electronApp, page, context, dir } = await setupElectron({ |     const { electronApp, page } = await setupElectron({ | ||||||
|       testInfo: this.testInfo, |       testInfo: this.testInfo, | ||||||
|       folderSetupFn: arg.folderSetupFn, |       folderSetupFn: arg.folderSetupFn, | ||||||
|       cleanProjectDir: arg.cleanProjectDir, |       cleanProjectDir: arg.cleanProjectDir, | ||||||
|       appSettings: arg.appSettings, |       appSettings: arg.appSettings, | ||||||
|     }) |     }) | ||||||
|     this.page = page |     this.page = page | ||||||
|     this.context = context |  | ||||||
|     this.electronApp = electronApp |     this.electronApp = electronApp | ||||||
|     this.dir = dir |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     // Easier to access throughout utils |  | ||||||
|     this.page.dir = dir |  | ||||||
|  |  | ||||||
|     // Setup localStorage, addCookies, reload |  | ||||||
|     await setup(this.context, this.page, this.testInfo) |  | ||||||
|  |  | ||||||
|     for (const key of unsafeTypedKeys(arg.fixtures)) { |     for (const key of unsafeTypedKeys(arg.fixtures)) { | ||||||
|       const fixture = arg.fixtures[key] |       const fixture = arg.fixtures[key] | ||||||
| @ -119,20 +110,32 @@ export class AuthenticatedTronApp { | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| export const fixtures = { | export const test = base.extend<Fixtures>({ | ||||||
|   cmdBar: async ({ page }: { page: Page }, use: any) => { |   app: async ({ page, context }, use, testInfo) => { | ||||||
|  |     await use(new AuthenticatedApp(context, page, testInfo)) | ||||||
|  |   }, | ||||||
|  |   tronApp: async ({ page, context }, use, testInfo) => { | ||||||
|  |     await use(new AuthenticatedTronApp(context, page, testInfo)) | ||||||
|  |   }, | ||||||
|  |   cmdBar: async ({ page }, use) => { | ||||||
|     await use(new CmdBarFixture(page)) |     await use(new CmdBarFixture(page)) | ||||||
|   }, |   }, | ||||||
|   editor: async ({ page }: { page: Page }, use: any) => { |   editor: async ({ page }, use) => { | ||||||
|     await use(new EditorFixture(page)) |     await use(new EditorFixture(page)) | ||||||
|   }, |   }, | ||||||
|   toolbar: async ({ page }: { page: Page }, use: any) => { |   toolbar: async ({ page }, use) => { | ||||||
|     await use(new ToolbarFixture(page)) |     await use(new ToolbarFixture(page)) | ||||||
|   }, |   }, | ||||||
|   scene: async ({ page }: { page: Page }, use: any) => { |   scene: async ({ page }, use) => { | ||||||
|     await use(new SceneFixture(page)) |     await use(new SceneFixture(page)) | ||||||
|   }, |   }, | ||||||
|   homePage: async ({ page }: { page: Page }, use: any) => { |   homePage: async ({ page }, use) => { | ||||||
|     await use(new HomePageFixture(page)) |     await use(new HomePageFixture(page)) | ||||||
|   }, |   }, | ||||||
| } | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | export { expect } from '@playwright/test' | ||||||
|  | |||||||
| @ -14,14 +14,10 @@ interface HomePageState { | |||||||
| export class HomePageFixture { | export class HomePageFixture { | ||||||
|   public page: Page |   public page: Page | ||||||
|  |  | ||||||
|   projectSection!: Locator |  | ||||||
|   projectCard!: Locator |   projectCard!: Locator | ||||||
|   projectCardTitle!: Locator |   projectCardTitle!: Locator | ||||||
|   projectCardFile!: Locator |   projectCardFile!: Locator | ||||||
|   projectCardFolder!: Locator |   projectCardFolder!: Locator | ||||||
|   projectButtonNew!: Locator |  | ||||||
|   projectButtonContinue!: Locator |  | ||||||
|   projectTextName!: Locator |  | ||||||
|   sortByDateBtn!: Locator |   sortByDateBtn!: Locator | ||||||
|   sortByNameBtn!: Locator |   sortByNameBtn!: Locator | ||||||
|  |  | ||||||
| @ -32,19 +28,11 @@ export class HomePageFixture { | |||||||
|   reConstruct = (page: Page) => { |   reConstruct = (page: Page) => { | ||||||
|     this.page = page |     this.page = page | ||||||
|  |  | ||||||
|     this.projectSection = this.page.getByTestId('home-section') |  | ||||||
|  |  | ||||||
|     this.projectCard = this.page.getByTestId('project-link') |     this.projectCard = this.page.getByTestId('project-link') | ||||||
|     this.projectCardTitle = this.page.getByTestId('project-title') |     this.projectCardTitle = this.page.getByTestId('project-title') | ||||||
|     this.projectCardFile = this.page.getByTestId('project-file-count') |     this.projectCardFile = this.page.getByTestId('project-file-count') | ||||||
|     this.projectCardFolder = this.page.getByTestId('project-folder-count') |     this.projectCardFolder = this.page.getByTestId('project-folder-count') | ||||||
|  |  | ||||||
|     this.projectButtonNew = this.page.getByTestId('home-new-file') |  | ||||||
|     this.projectTextName = this.page.getByTestId('cmd-bar-arg-value') |  | ||||||
|     this.projectButtonContinue = this.page.getByRole('button', { |  | ||||||
|       name: 'Continue', |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified') |     this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified') | ||||||
|     this.sortByNameBtn = this.page.getByTestId('home-sort-by-name') |     this.sortByNameBtn = this.page.getByTestId('home-sort-by-name') | ||||||
|   } |   } | ||||||
| @ -103,25 +91,10 @@ export class HomePageFixture { | |||||||
|       .toEqual(expectedState) |       .toEqual(expectedState) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   createAndGoToProject = async (projectTitle: string) => { |  | ||||||
|     await expect(this.projectSection).not.toHaveText('Loading your Projects...') |  | ||||||
|     await this.projectButtonNew.click() |  | ||||||
|     await this.projectTextName.click() |  | ||||||
|     await this.projectTextName.fill(projectTitle) |  | ||||||
|     await this.projectButtonContinue.click() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   openProject = async (projectTitle: string) => { |   openProject = async (projectTitle: string) => { | ||||||
|     const projectCard = this.projectCard.locator( |     const projectCard = this.projectCard.locator( | ||||||
|       this.page.getByText(projectTitle) |       this.page.getByText(projectTitle) | ||||||
|     ) |     ) | ||||||
|     await projectCard.click() |     await projectCard.click() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   goToModelingScene = async (name: string = 'testDefault') => { |  | ||||||
|     // On web this is a no-op. There is no project view. |  | ||||||
|     if (process.env.PLATFORM === 'web') return |  | ||||||
|  |  | ||||||
|     await this.createAndGoToProject(name) |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -53,9 +53,8 @@ export class SceneFixture { | |||||||
|  |  | ||||||
|   expectState = async (expected: SceneSerialised) => { |   expectState = async (expected: SceneSerialised) => { | ||||||
|     return expect |     return expect | ||||||
|       .poll(async () => await this._serialiseScene(), { |       .poll(() => this._serialiseScene(), { | ||||||
|         intervals: [1_000, 2_000, 10_000], |         message: `Expected scene state to match`, | ||||||
|         timeout: 60000, |  | ||||||
|       }) |       }) | ||||||
|       .toEqual(expected) |       .toEqual(expected) | ||||||
|   } |   } | ||||||
| @ -188,10 +187,7 @@ export class SceneFixture { | |||||||
|         type: 'default_camera_get_settings', |         type: 'default_camera_get_settings', | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
|     await this.page |     await this.waitForExecutionDone() | ||||||
|       .locator(`[data-receive-command-type="default_camera_get_settings"]`) |  | ||||||
|       .first() |  | ||||||
|       .waitFor() |  | ||||||
|     const position = await Promise.all([ |     const position = await Promise.all([ | ||||||
|       this.page.getByTestId('cam-x-position').inputValue().then(Number), |       this.page.getByTestId('cam-x-position').inputValue().then(Number), | ||||||
|       this.page.getByTestId('cam-y-position').inputValue().then(Number), |       this.page.getByTestId('cam-y-position').inputValue().then(Number), | ||||||
| @ -218,34 +214,10 @@ export class SceneFixture { | |||||||
|     coords: { x: number; y: number }, |     coords: { x: number; y: number }, | ||||||
|     diff: number |     diff: number | ||||||
|   ) => { |   ) => { | ||||||
|     await expectPixelColor(this.page, colour, coords, diff) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get gizmo() { |  | ||||||
|     return this.page.locator('[aria-label*=gizmo]') |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   async clickGizmoMenuItem(name: string) { |  | ||||||
|     await this.gizmo.hover() |  | ||||||
|     await this.gizmo.click({ button: 'right' }) |  | ||||||
|     const buttonToTest = this.page.getByRole('button', { |  | ||||||
|       name: name, |  | ||||||
|     }) |  | ||||||
|     await expect(buttonToTest).toBeVisible() |  | ||||||
|     await buttonToTest.click() |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export async function expectPixelColor( |  | ||||||
|   page: Page, |  | ||||||
|   colour: [number, number, number], |  | ||||||
|   coords: { x: number; y: number }, |  | ||||||
|   diff: number |  | ||||||
| ) { |  | ||||||
|     let finalValue = colour |     let finalValue = colour | ||||||
|     await expect |     await expect | ||||||
|       .poll(async () => { |       .poll(async () => { | ||||||
|       const pixel = (await getPixelRGBs(page)(coords, 1))[0] |         const pixel = (await getPixelRGBs(this.page)(coords, 1))[0] | ||||||
|         if (!pixel) return null |         if (!pixel) return null | ||||||
|         finalValue = pixel |         finalValue = pixel | ||||||
|         return pixel.every( |         return pixel.every( | ||||||
| @ -260,3 +232,17 @@ export async function expectPixelColor( | |||||||
|         ) |         ) | ||||||
|       }) |       }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   get gizmo() { | ||||||
|  |     return this.page.locator('[aria-label*=gizmo]') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   async clickGizmoMenuItem(name: string) { | ||||||
|  |     await this.gizmo.click({ button: 'right' }) | ||||||
|  |     const buttonToTest = this.page.getByRole('button', { | ||||||
|  |       name: name, | ||||||
|  |     }) | ||||||
|  |     await expect(buttonToTest).toBeVisible() | ||||||
|  |     await buttonToTest.click() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,13 +1,6 @@ | |||||||
| import type { Page, Locator } from '@playwright/test' | import type { Page, Locator } from '@playwright/test' | ||||||
| import { expect } from '../zoo-test' | import { expect } from './fixtureSetup' | ||||||
| import { | import { doAndWaitForImageDiff } from '../test-utils' | ||||||
|   checkIfPaneIsOpen, |  | ||||||
|   closePane, |  | ||||||
|   doAndWaitForImageDiff, |  | ||||||
|   openPane, |  | ||||||
| } from '../test-utils' |  | ||||||
| import { SidebarType } from 'components/ModelingSidebar/ModelingPanes' |  | ||||||
| import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants' |  | ||||||
|  |  | ||||||
| export class ToolbarFixture { | export class ToolbarFixture { | ||||||
|   public page: Page |   public page: Page | ||||||
| @ -27,10 +20,6 @@ export class ToolbarFixture { | |||||||
|   filePane!: Locator |   filePane!: Locator | ||||||
|   exeIndicator!: Locator |   exeIndicator!: Locator | ||||||
|   treeInputField!: Locator |   treeInputField!: Locator | ||||||
|   /** The sidebar button for the Feature Tree pane */ |  | ||||||
|   featureTreeId = 'feature-tree' as const |  | ||||||
|   /** The pane element for the Feature Tree */ |  | ||||||
|   featureTreePane!: Locator |  | ||||||
|  |  | ||||||
|   constructor(page: Page) { |   constructor(page: Page) { | ||||||
|     this.page = page |     this.page = page | ||||||
| @ -52,7 +41,6 @@ export class ToolbarFixture { | |||||||
|     this.treeInputField = page.getByTestId('tree-input-field') |     this.treeInputField = page.getByTestId('tree-input-field') | ||||||
|  |  | ||||||
|     this.filePane = page.locator('#files-pane') |     this.filePane = page.locator('#files-pane') | ||||||
|     this.featureTreePane = page.locator('#feature-tree-pane') |  | ||||||
|     this.fileCreateToast = page.getByText('Successfully created') |     this.fileCreateToast = page.getByText('Successfully created') | ||||||
|     this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') |     this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') | ||||||
|   } |   } | ||||||
| @ -103,76 +91,4 @@ export class ToolbarFixture { | |||||||
|       await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 }) |       await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async closePane(paneId: SidebarType) { |  | ||||||
|     return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX) |  | ||||||
|   } |  | ||||||
|   async openPane(paneId: SidebarType) { |  | ||||||
|     return openPane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX) |  | ||||||
|   } |  | ||||||
|   async checkIfPaneIsOpen(paneId: SidebarType) { |  | ||||||
|     return checkIfPaneIsOpen(this.page, paneId + SIDEBAR_BUTTON_SUFFIX) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   async openFeatureTreePane() { |  | ||||||
|     return this.openPane(this.featureTreeId) |  | ||||||
|   } |  | ||||||
|   async closeFeatureTreePane() { |  | ||||||
|     await this.closePane(this.featureTreeId) |  | ||||||
|   } |  | ||||||
|   async checkIfFeatureTreePaneIsOpen() { |  | ||||||
|     return this.checkIfPaneIsOpen(this.featureTreeId) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Get a specific operation button from the Feature Tree pane |  | ||||||
|    */ |  | ||||||
|   async getFeatureTreeOperation(operationName: string, operationIndex: number) { |  | ||||||
|     await this.openFeatureTreePane() |  | ||||||
|     await expect(this.featureTreePane).toBeVisible() |  | ||||||
|     return this.featureTreePane |  | ||||||
|       .getByRole('button', { |  | ||||||
|         name: operationName, |  | ||||||
|       }) |  | ||||||
|       .nth(operationIndex) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * View source on a specific operation in the Feature Tree pane. |  | ||||||
|    * @param operationName The name of the operation type |  | ||||||
|    * @param operationIndex The index out of operations of this type |  | ||||||
|    */ |  | ||||||
|   async viewSourceOnOperation(operationName: string, operationIndex: number) { |  | ||||||
|     const operationButton = await this.getFeatureTreeOperation( |  | ||||||
|       operationName, |  | ||||||
|       operationIndex |  | ||||||
|     ) |  | ||||||
|     const viewSourceMenuButton = this.page.getByRole('button', { |  | ||||||
|       name: 'View KCL source code', |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await operationButton.click({ button: 'right' }) |  | ||||||
|     await expect(viewSourceMenuButton).toBeVisible() |  | ||||||
|     await viewSourceMenuButton.click() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Go to definition on a specific operation in the Feature Tree pane |  | ||||||
|    */ |  | ||||||
|   async goToDefinitionOnOperation( |  | ||||||
|     operationName: string, |  | ||||||
|     operationIndex: number |  | ||||||
|   ) { |  | ||||||
|     const operationButton = await this.getFeatureTreeOperation( |  | ||||||
|       operationName, |  | ||||||
|       operationIndex |  | ||||||
|     ) |  | ||||||
|     const goToDefinitionMenuButton = this.page.getByRole('button', { |  | ||||||
|       name: 'View function definition', |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await operationButton.click({ button: 'right' }) |  | ||||||
|     await expect(goToDefinitionMenuButton).toBeVisible() |  | ||||||
|     await goToDefinitionMenuButton.click() |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,22 +1,29 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
| import { executorInputPath } from './test-utils' | import { setupElectron, tearDown, executorInputPath } from './test-utils' | ||||||
| import { join } from 'path' | import { join } from 'path' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test( | test( | ||||||
|   'When machine-api server not found butt is disabled and shows the reason', |   'When machine-api server not found butt is disabled and shows the reason', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ context, page }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
|     await context.folderSetupFn(async (dir) => { |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|         const bracketDir = join(dir, 'bracket') |         const bracketDir = join(dir, 'bracket') | ||||||
|         await fsp.mkdir(bracketDir, { recursive: true }) |         await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|         await fsp.copyFile( |         await fsp.copyFile( | ||||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), |           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|           join(bracketDir, 'main.kcl') |           join(bracketDir, 'main.kcl') | ||||||
|         ) |         ) | ||||||
|  |       }, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await expect(page.getByText('bracket')).toBeVisible() |     await expect(page.getByText('bracket')).toBeVisible() | ||||||
|  |  | ||||||
| @ -40,23 +47,28 @@ test( | |||||||
|     // that the machine-api server is not found |     // that the machine-api server is not found | ||||||
|     await makeButton.hover() |     await makeButton.hover() | ||||||
|     await expect(page.getByText(notFoundText).first()).toBeVisible() |     await expect(page.getByText(notFoundText).first()).toBeVisible() | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| test( | test( | ||||||
|   'When machine-api server not found home screen & project status shows the reason', |   'When machine-api server not found home screen & project status shows the reason', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ context, page }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
|     await context.folderSetupFn(async (dir) => { |     const { electronApp, page } = await setupElectron({ | ||||||
|  |       testInfo, | ||||||
|  |       folderSetupFn: async (dir) => { | ||||||
|         const bracketDir = join(dir, 'bracket') |         const bracketDir = join(dir, 'bracket') | ||||||
|         await fsp.mkdir(bracketDir, { recursive: true }) |         await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|         await fsp.copyFile( |         await fsp.copyFile( | ||||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), |           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|           join(bracketDir, 'main.kcl') |           join(bracketDir, 'main.kcl') | ||||||
|         ) |         ) | ||||||
|  |       }, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     const notFoundText = 'Machine API server was not discovered' |     const notFoundText = 'Machine API server was not discovered' | ||||||
|  |  | ||||||
| @ -79,5 +91,7 @@ test( | |||||||
|  |  | ||||||
|     await networkMachineToggle.hover() |     await networkMachineToggle.hover() | ||||||
|     await expect(page.getByText(notFoundText).nth(1)).toBeVisible() |     await expect(page.getByText(notFoundText).nth(1)).toBeVisible() | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1,12 +0,0 @@ | |||||||
| // These tests are meant to simply test starting and stopping the electron |  | ||||||
| // application, check it can make it to the project pane, and nothing more. |  | ||||||
| // It also tests our test wrappers are working. |  | ||||||
| // Additionally this serves as a nice minimal example. |  | ||||||
|  |  | ||||||
| import { test, expect } from './zoo-test' |  | ||||||
|  |  | ||||||
| test.describe('Open the application', () => { |  | ||||||
|   test('see the project view', async ({ page, context }) => { |  | ||||||
|     await expect(page.getByTestId('home-section')).toBeVisible() |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -1,78 +1,79 @@ | |||||||
| import { test, expect } from './zoo-test' | import { test, expect } from '@playwright/test' | ||||||
| import { join } from 'path' | import { join } from 'path' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
| import { getUtils, executorInputPath, createProject } from './test-utils' | import { | ||||||
|  |   getUtils, | ||||||
|  |   setup, | ||||||
|  |   setupElectron, | ||||||
|  |   tearDown, | ||||||
|  |   executorInputPath, | ||||||
|  |   createProject, | ||||||
|  | } from './test-utils' | ||||||
| import { bracket } from 'lib/exampleKcl' | import { bracket } from 'lib/exampleKcl' | ||||||
| import { onboardingPaths } from 'routes/Onboarding/paths' | import { onboardingPaths } from 'routes/Onboarding/paths' | ||||||
| import { | import { | ||||||
|   TEST_SETTINGS_KEY, |   TEST_SETTINGS_KEY, | ||||||
|   TEST_SETTINGS_ONBOARDING_START, |   TEST_SETTINGS_ONBOARDING_START, | ||||||
|   TEST_SETTINGS_ONBOARDING_EXPORT, |   TEST_SETTINGS_ONBOARDING_EXPORT, | ||||||
|  |   TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING, | ||||||
|   TEST_SETTINGS_ONBOARDING_USER_MENU, |   TEST_SETTINGS_ONBOARDING_USER_MENU, | ||||||
| } from './storageStates' | } from './storageStates' | ||||||
| import * as TOML from '@iarna/toml' | import * as TOML from '@iarna/toml' | ||||||
| import { expectPixelColor } from './fixtures/sceneFixture' |  | ||||||
|  |  | ||||||
| // Because onboarding relies on an app setting we need to set it as incompletel | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
| // for all these tests. |   if (testInfo.tags.includes('@electron')) { | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |   await setup(context, page) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Onboarding tests', () => { | test.describe('Onboarding tests', () => { | ||||||
|   test( |   test('Onboarding code is shown in the editor', async ({ page }) => { | ||||||
|     'Onboarding code is shown in the editor', |  | ||||||
|     { |  | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: 'incomplete', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       cleanProjectDir: true, |  | ||||||
|     }, |  | ||||||
|     async ({ context, page, homePage }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |  | ||||||
|       await homePage.goToModelingScene() |     // Override beforeEach test setup | ||||||
|  |     await page.addInitScript( | ||||||
|  |       async ({ settingsKey }) => { | ||||||
|  |         // Give no initial code, so that the onboarding start is shown immediately | ||||||
|  |         localStorage.removeItem('persistCode') | ||||||
|  |         localStorage.removeItem(settingsKey) | ||||||
|  |       }, | ||||||
|  |       { settingsKey: TEST_SETTINGS_KEY } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // Test that the onboarding pane loaded |     // Test that the onboarding pane loaded | ||||||
|       await expect( |     await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible() | ||||||
|         page.getByText('Welcome to Modeling App! This') |  | ||||||
|       ).toBeVisible() |  | ||||||
|  |  | ||||||
|       // Test that the onboarding pane loaded |  | ||||||
|       await expect( |  | ||||||
|         page.getByText('Welcome to Modeling App! This') |  | ||||||
|       ).toBeVisible() |  | ||||||
|  |  | ||||||
|     // *and* that the code is shown in the editor |     // *and* that the code is shown in the editor | ||||||
|       await expect(page.locator('.cm-content')).toContainText( |     await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') | ||||||
|         '// Shelf Bracket' |   }) | ||||||
|       ) |  | ||||||
|  |  | ||||||
|       // Make sure the model loaded |  | ||||||
|       const XYPlanePoint = { x: 774, y: 116 } as const |  | ||||||
|       const modelColor: [number, number, number] = [45, 45, 45] |  | ||||||
|       await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) |  | ||||||
|       expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan( |  | ||||||
|         8 |  | ||||||
|       ) |  | ||||||
|     } |  | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   test( |   test( | ||||||
|     'Desktop: fresh onboarding executes and loads', |     'Desktop: fresh onboarding executes and loads', | ||||||
|     { |     { tag: '@electron' }, | ||||||
|       tag: '@electron', |     async ({ browserName: _ }, testInfo) => { | ||||||
|  |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|         appSettings: { |         appSettings: { | ||||||
|           app: { |           app: { | ||||||
|             onboardingStatus: 'incomplete', |             onboardingStatus: 'incomplete', | ||||||
|           }, |           }, | ||||||
|         }, |         }, | ||||||
|         cleanProjectDir: true, |         cleanProjectDir: true, | ||||||
|     }, |       }) | ||||||
|     async ({ page, homePage }, testInfo) => { |  | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|  |  | ||||||
|       const viewportSize = { width: 1200, height: 500 } |       const viewportSize = { width: 1200, height: 500 } | ||||||
|       await page.setBodyDimensions(viewportSize) |       await page.setViewportSize(viewportSize) | ||||||
|  |  | ||||||
|       await test.step(`Create a project and open to the onboarding`, async () => { |       await test.step(`Create a project and open to the onboarding`, async () => { | ||||||
|         await createProject({ name: 'project-link', page }) |         await createProject({ name: 'project-link', page }) | ||||||
| @ -91,80 +92,61 @@ test.describe('Onboarding tests', () => { | |||||||
|         await expect(page.locator('.cm-content')).toContainText( |         await expect(page.locator('.cm-content')).toContainText( | ||||||
|           '// Shelf Bracket' |           '// Shelf Bracket' | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         // TODO: jess make less shit |  | ||||||
|         // Make sure the model loaded |  | ||||||
|         //const XYPlanePoint = { x: 986, y: 522 } as const |  | ||||||
|         //const modelColor: [number, number, number] = [76, 76, 76] |  | ||||||
|         //await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) |  | ||||||
|  |  | ||||||
|         //await expectPixelColor(page, modelColor, XYPlanePoint, 8) |  | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   test( |   test('Code resets after confirmation', async ({ page }) => { | ||||||
|     'Code resets after confirmation', |  | ||||||
|     { |  | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: 'incomplete', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       cleanProjectDir: true, |  | ||||||
|     }, |  | ||||||
|     async ({ context, page, homePage }) => { |  | ||||||
|     const initialCode = `sketch001 = startSketchOn('XZ')` |     const initialCode = `sketch001 = startSketchOn('XZ')` | ||||||
|  |  | ||||||
|     // Load the page up with some code so we see the confirmation warning |     // Load the page up with some code so we see the confirmation warning | ||||||
|     // when we go to replay onboarding |     // when we go to replay onboarding | ||||||
|       await context.addInitScript((code) => { |     await page.addInitScript((code) => { | ||||||
|       localStorage.setItem('persistCode', code) |       localStorage.setItem('persistCode', code) | ||||||
|     }, initialCode) |     }, initialCode) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     const u = await getUtils(page) | ||||||
|       await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // Replay the onboarding |     // Replay the onboarding | ||||||
|     await page.getByRole('link', { name: 'Settings' }).last().click() |     await page.getByRole('link', { name: 'Settings' }).last().click() | ||||||
|       const replayButton = page.getByRole('button', { |     const replayButton = page.getByRole('button', { name: 'Replay onboarding' }) | ||||||
|         name: 'Replay onboarding', |  | ||||||
|       }) |  | ||||||
|     await expect(replayButton).toBeVisible() |     await expect(replayButton).toBeVisible() | ||||||
|     await replayButton.click() |     await replayButton.click() | ||||||
|  |  | ||||||
|     // Ensure we see the warning, and that the code has not yet updated |     // Ensure we see the warning, and that the code has not yet updated | ||||||
|       await expect(page.getByText('Would you like to create')).toBeVisible() |     await expect( | ||||||
|  |       page.getByText('Replaying onboarding resets your code') | ||||||
|  |     ).toBeVisible() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(initialCode) |     await expect(page.locator('.cm-content')).toHaveText(initialCode) | ||||||
|  |  | ||||||
|     const nextButton = page.getByTestId('onboarding-next') |     const nextButton = page.getByTestId('onboarding-next') | ||||||
|       await nextButton.hover() |     await expect(nextButton).toBeVisible() | ||||||
|     await nextButton.click() |     await nextButton.click() | ||||||
|  |  | ||||||
|     // Ensure we see the introduction and that the code has been reset |     // Ensure we see the introduction and that the code has been reset | ||||||
|     await expect(page.getByText('Welcome to Modeling App!')).toBeVisible() |     await expect(page.getByText('Welcome to Modeling App!')).toBeVisible() | ||||||
|       await expect(page.locator('.cm-content')).toContainText( |     await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') | ||||||
|         '// Shelf Bracket' |  | ||||||
|       ) |  | ||||||
|  |  | ||||||
|       // There used to be old code here that checked if we stored the reset |     // Ensure we persisted the code to local storage. | ||||||
|       // code into localStorage but that isn't the case on desktop. It gets |     // Playwright's addInitScript method unfortunately will reset | ||||||
|       // saved to the file system, which we have other tests for. |     // this code if we try reloading the page as a test, | ||||||
|     } |     // so this is our best way to test persistence afaik. | ||||||
|   ) |     expect( | ||||||
|  |       await page.evaluate(() => { | ||||||
|  |         return localStorage.getItem('persistCode') | ||||||
|  |       }) | ||||||
|  |     ).toContain('// Shelf Bracket') | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   test('Click through each onboarding step', async ({ page }) => { | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|   test( |  | ||||||
|     'Click through each onboarding step', |  | ||||||
|     { |  | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: 'incomplete', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     async ({ context, page, homePage }) => { |  | ||||||
|     // Override beforeEach test setup |     // Override beforeEach test setup | ||||||
|       await context.addInitScript( |     await page.addInitScript( | ||||||
|       async ({ settingsKey, settings }) => { |       async ({ settingsKey, settings }) => { | ||||||
|         // Give no initial code, so that the onboarding start is shown immediately |         // Give no initial code, so that the onboarding start is shown immediately | ||||||
|         localStorage.setItem('persistCode', '') |         localStorage.setItem('persistCode', '') | ||||||
| @ -172,113 +154,107 @@ test.describe('Onboarding tests', () => { | |||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         settingsKey: TEST_SETTINGS_KEY, |         settingsKey: TEST_SETTINGS_KEY, | ||||||
|           settings: TOML.stringify({ |         settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }), | ||||||
|             settings: TEST_SETTINGS_ONBOARDING_START, |  | ||||||
|           }), |  | ||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 1080 }) |     await page.setViewportSize({ width: 1200, height: 1080 }) | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // Test that the onboarding pane loaded |     // Test that the onboarding pane loaded | ||||||
|       await expect( |     await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible() | ||||||
|         page.getByText('Welcome to Modeling App! This') |  | ||||||
|       ).toBeVisible() |  | ||||||
|  |  | ||||||
|     const nextButton = page.getByTestId('onboarding-next') |     const nextButton = page.getByTestId('onboarding-next') | ||||||
|  |  | ||||||
|     while ((await nextButton.innerText()) !== 'Finish') { |     while ((await nextButton.innerText()) !== 'Finish') { | ||||||
|         await nextButton.hover() |       await expect(nextButton).toBeVisible() | ||||||
|       await nextButton.click() |       await nextButton.click() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Finish the onboarding |     // Finish the onboarding | ||||||
|       await nextButton.hover() |     await expect(nextButton).toBeVisible() | ||||||
|     await nextButton.click() |     await nextButton.click() | ||||||
|  |  | ||||||
|     // Test that the onboarding pane is gone |     // Test that the onboarding pane is gone | ||||||
|     await expect(page.getByTestId('onboarding-content')).not.toBeVisible() |     await expect(page.getByTestId('onboarding-content')).not.toBeVisible() | ||||||
|       await expect.poll(() => page.url()).not.toContain('/onboarding') |     await expect(page.url()).not.toContain('onboarding') | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   test( |   test('Onboarding redirects and code updating', async ({ page }) => { | ||||||
|     'Onboarding redirects and code updating', |     const u = await getUtils(page) | ||||||
|     { |  | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: '/export', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       cleanProjectDir: true, |  | ||||||
|     }, |  | ||||||
|     async ({ context, page, homePage }) => { |  | ||||||
|       const originalCode = 'sigmaAllow = 15000' |  | ||||||
|  |  | ||||||
|     // Override beforeEach test setup |     // Override beforeEach test setup | ||||||
|       await context.addInitScript( |     await page.addInitScript( | ||||||
|       async ({ settingsKey, settings }) => { |       async ({ settingsKey, settings }) => { | ||||||
|         // Give some initial code, so we can test that it's cleared |         // Give some initial code, so we can test that it's cleared | ||||||
|           localStorage.setItem('persistCode', originalCode) |         localStorage.setItem('persistCode', 'sigmaAllow = 15000') | ||||||
|         localStorage.setItem(settingsKey, settings) |         localStorage.setItem(settingsKey, settings) | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         settingsKey: TEST_SETTINGS_KEY, |         settingsKey: TEST_SETTINGS_KEY, | ||||||
|           settings: TOML.stringify({ |         settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }), | ||||||
|             settings: TEST_SETTINGS_ONBOARDING_EXPORT, |  | ||||||
|           }), |  | ||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // Test that the redirect happened |     // Test that the redirect happened | ||||||
|       await expect.poll(() => page.url()).toContain('/onboarding/export') |     await expect(page.url().split(':3000').slice(-1)[0]).toBe( | ||||||
|  |       `/file/%2Fbrowser%2Fmain.kcl/onboarding/export` | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     // Test that you come back to this page when you refresh |     // Test that you come back to this page when you refresh | ||||||
|     await page.reload() |     await page.reload() | ||||||
|       await expect.poll(() => page.url()).toContain('/onboarding/export') |     await expect(page.url().split(':3000').slice(-1)[0]).toBe( | ||||||
|  |       `/file/%2Fbrowser%2Fmain.kcl/onboarding/export` | ||||||
|       // Test that the code changes when you advance to the next step |     ) | ||||||
|       await page.getByTestId('onboarding-next').hover() |  | ||||||
|       await page.getByTestId('onboarding-next').click() |  | ||||||
|  |  | ||||||
|     // Test that the onboarding pane loaded |     // Test that the onboarding pane loaded | ||||||
|     const title = page.locator('[data-testid="onboarding-content"]') |     const title = page.locator('[data-testid="onboarding-content"]') | ||||||
|     await expect(title).toBeAttached() |     await expect(title).toBeAttached() | ||||||
|  |  | ||||||
|       await expect(page.locator('.cm-content')).not.toHaveText(originalCode) |     // Test that the code changes when you advance to the next step | ||||||
|  |     await page.locator('[data-testid="onboarding-next"]').click() | ||||||
|  |     await expect(page.locator('.cm-content')).toHaveText('') | ||||||
|  |  | ||||||
|     // Test that the code is not empty when you click on the next step |     // Test that the code is not empty when you click on the next step | ||||||
|       await page.locator('[data-testid="onboarding-next"]').hover() |  | ||||||
|     await page.locator('[data-testid="onboarding-next"]').click() |     await page.locator('[data-testid="onboarding-next"]').click() | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(/.+/) |     await expect(page.locator('.cm-content')).toHaveText(/.+/) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   test('Onboarding code gets reset to demo on Interactive Numbers step', async ({ | ||||||
|  |     page, | ||||||
|  |   }) => { | ||||||
|  |     test.skip( | ||||||
|  |       process.platform === 'darwin', | ||||||
|  |       "Skip on macOS, because Playwright isn't behaving the same as the actual browser" | ||||||
|  |     ) | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |     const badCode = `// This is bad code we shouldn't see` | ||||||
|  |     // Override beforeEach test setup | ||||||
|  |     await page.addInitScript( | ||||||
|  |       async ({ settingsKey, settings, badCode }) => { | ||||||
|  |         localStorage.setItem('persistCode', badCode) | ||||||
|  |         localStorage.setItem(settingsKey, settings) | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         settingsKey: TEST_SETTINGS_KEY, | ||||||
|  |         settings: TOML.stringify({ | ||||||
|  |           settings: TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING, | ||||||
|  |         }), | ||||||
|  |         badCode, | ||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|   test( |     await page.setViewportSize({ width: 1200, height: 1080 }) | ||||||
|     'Onboarding code gets reset to demo on Interactive Numbers step', |     await u.waitForAuthSkipAppStart() | ||||||
|     { |  | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: '/parametric-modeling', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       cleanProjectDir: true, |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     async ({ context, page, homePage }) => { |     await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, { | ||||||
|       const u = await getUtils(page) |       waitUntil: 'domcontentloaded', | ||||||
|       const badCode = `// This is bad code we shouldn't see` |     }) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 1080 }) |  | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|       await expect |  | ||||||
|         .poll(() => page.url()) |  | ||||||
|         .toContain(onboardingPaths.PARAMETRIC_MODELING) |  | ||||||
|  |  | ||||||
|     const bracketNoNewLines = bracket.replace(/\n/g, '') |     const bracketNoNewLines = bracket.replace(/\n/g, '') | ||||||
|  |  | ||||||
| @ -294,7 +270,6 @@ test.describe('Onboarding tests', () => { | |||||||
|     await expect(u.codeLocator).toHaveText(badCode) |     await expect(u.codeLocator).toHaveText(badCode) | ||||||
|  |  | ||||||
|     // Click to the next step |     // Click to the next step | ||||||
|       await page.locator('[data-testid="onboarding-next"]').hover() |  | ||||||
|     await page.locator('[data-testid="onboarding-next"]').click() |     await page.locator('[data-testid="onboarding-next"]').click() | ||||||
|     await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, { |     await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, { | ||||||
|       waitUntil: 'domcontentloaded', |       waitUntil: 'domcontentloaded', | ||||||
| @ -302,25 +277,13 @@ test.describe('Onboarding tests', () => { | |||||||
|  |  | ||||||
|     // Check that the code has been reset |     // Check that the code has been reset | ||||||
|     await expect(u.codeLocator).toHaveText(bracketNoNewLines) |     await expect(u.codeLocator).toHaveText(bracketNoNewLines) | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   // (lee) The two avatar tests are weird because even on main, we don't have |   test('Avatar text updates depending on image load success', async ({ | ||||||
|   // anything to do with the avatar inside the onboarding test. Due to the |     page, | ||||||
|   // low impact of an avatar not showing I'm changing this to fixme. |   }) => { | ||||||
|   test.fixme( |  | ||||||
|     'Avatar text updates depending on image load success', |  | ||||||
|     { |  | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: 'incomplete', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       cleanProjectDir: true, |  | ||||||
|     }, |  | ||||||
|     async ({ context, page, homePage }) => { |  | ||||||
|     // Override beforeEach test setup |     // Override beforeEach test setup | ||||||
|       await context.addInitScript( |     await page.addInitScript( | ||||||
|       async ({ settingsKey, settings }) => { |       async ({ settingsKey, settings }) => { | ||||||
|         localStorage.setItem(settingsKey, settings) |         localStorage.setItem(settingsKey, settings) | ||||||
|       }, |       }, | ||||||
| @ -332,8 +295,11 @@ test.describe('Onboarding tests', () => { | |||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     const u = await getUtils(page) | ||||||
|       await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |     await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) | ||||||
|  |  | ||||||
|     // Test that the text in this step is correct |     // Test that the text in this step is correct | ||||||
|     const avatarLocator = await page |     const avatarLocator = await page | ||||||
| @ -361,16 +327,13 @@ test.describe('Onboarding tests', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     // 404 the CI avatar image |     // 404 the CI avatar image | ||||||
|       await page.route( |     await page.route('https://lh3.googleusercontent.com/**', async (route) => { | ||||||
|         'https://lh3.googleusercontent.com/**', |  | ||||||
|         async (route) => { |  | ||||||
|       await route.fulfill({ |       await route.fulfill({ | ||||||
|         status: 404, |         status: 404, | ||||||
|         contentType: 'text/plain', |         contentType: 'text/plain', | ||||||
|         body: 'Not Found!', |         body: 'Not Found!', | ||||||
|       }) |       }) | ||||||
|         } |     }) | ||||||
|       ) |  | ||||||
|  |  | ||||||
|     await page.reload({ waitUntil: 'domcontentloaded' }) |     await page.reload({ waitUntil: 'domcontentloaded' }) | ||||||
|  |  | ||||||
| @ -378,22 +341,13 @@ test.describe('Onboarding tests', () => { | |||||||
|     await expect(avatarLocator).not.toBeVisible() |     await expect(avatarLocator).not.toBeVisible() | ||||||
|     await expect(onboardingOverlayLocator).toBeVisible() |     await expect(onboardingOverlayLocator).toBeVisible() | ||||||
|     await expect(onboardingOverlayLocator).toContainText('the menu button') |     await expect(onboardingOverlayLocator).toContainText('the menu button') | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   test.fixme( |   test("Avatar text doesn't mention avatar when no avatar", async ({ | ||||||
|     "Avatar text doesn't mention avatar when no avatar", |     page, | ||||||
|     { |   }) => { | ||||||
|       appSettings: { |  | ||||||
|         app: { |  | ||||||
|           onboardingStatus: 'incomplete', |  | ||||||
|         }, |  | ||||||
|       }, |  | ||||||
|       cleanProjectDir: true, |  | ||||||
|     }, |  | ||||||
|     async ({ context, page, homePage }) => { |  | ||||||
|     // Override beforeEach test setup |     // Override beforeEach test setup | ||||||
|       await context.addInitScript( |     await page.addInitScript( | ||||||
|       async ({ settingsKey, settings }) => { |       async ({ settingsKey, settings }) => { | ||||||
|         localStorage.setItem(settingsKey, settings) |         localStorage.setItem(settingsKey, settings) | ||||||
|         localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE') |         localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE') | ||||||
| @ -406,8 +360,11 @@ test.describe('Onboarding tests', () => { | |||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |     const u = await getUtils(page) | ||||||
|       await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |     await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) | ||||||
|  |  | ||||||
|     // Test that the text in this step is correct |     // Test that the text in this step is correct | ||||||
|     const sidebar = page.getByTestId('user-sidebar-toggle') |     const sidebar = page.getByTestId('user-sidebar-toggle') | ||||||
| @ -433,28 +390,23 @@ test.describe('Onboarding tests', () => { | |||||||
|     for (const feature of userMenuFeatures) { |     for (const feature of userMenuFeatures) { | ||||||
|       await expect(onboardingOverlayLocator).toContainText(feature) |       await expect(onboardingOverlayLocator).toContainText(feature) | ||||||
|     } |     } | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test.fixme( | test( | ||||||
|   'Restarting onboarding on desktop takes one attempt', |   'Restarting onboarding on desktop takes one attempt', | ||||||
|   { |   { tag: '@electron' }, | ||||||
|     appSettings: { |   async ({ browser: _ }, testInfo) => { | ||||||
|       app: { |     const { electronApp, page } = await setupElectron({ | ||||||
|         onboardingStatus: 'dismissed', |       testInfo, | ||||||
|       }, |       folderSetupFn: async (dir) => { | ||||||
|     }, |  | ||||||
|     cleanProjectDir: true, |  | ||||||
|   }, |  | ||||||
|   async ({ context, page, homePage }, testInfo) => { |  | ||||||
|     await context.folderSetupFn(async (dir) => { |  | ||||||
|         const routerTemplateDir = join(dir, 'router-template-slate') |         const routerTemplateDir = join(dir, 'router-template-slate') | ||||||
|         await fsp.mkdir(routerTemplateDir, { recursive: true }) |         await fsp.mkdir(routerTemplateDir, { recursive: true }) | ||||||
|         await fsp.copyFile( |         await fsp.copyFile( | ||||||
|           executorInputPath('router-template-slate.kcl'), |           executorInputPath('router-template-slate.kcl'), | ||||||
|           join(routerTemplateDir, 'main.kcl') |           join(routerTemplateDir, 'main.kcl') | ||||||
|         ) |         ) | ||||||
|  |       }, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     // Our constants |     // Our constants | ||||||
| @ -466,8 +418,9 @@ test.fixme( | |||||||
|     const restartOnboardingButton = page.getByRole('button', { |     const restartOnboardingButton = page.getByRole('button', { | ||||||
|       name: 'Reset onboarding', |       name: 'Reset onboarding', | ||||||
|     }) |     }) | ||||||
|     const nextButton = page.getByTestId('onboarding-next') |     const restartConfirmationButton = page.getByRole('button', { | ||||||
|  |       name: 'Make a new project', | ||||||
|  |     }) | ||||||
|     const tutorialProjectIndicator = page |     const tutorialProjectIndicator = page | ||||||
|       .getByTestId('project-sidebar-toggle') |       .getByTestId('project-sidebar-toggle') | ||||||
|       .filter({ hasText: 'Tutorial Project 00' }) |       .filter({ hasText: 'Tutorial Project 00' }) | ||||||
| @ -486,7 +439,7 @@ test.fixme( | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Navigate into project', async () => { |     await test.step('Navigate into project', async () => { | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       page.on('console', console.log) |       page.on('console', console.log) | ||||||
|  |  | ||||||
| @ -502,22 +455,14 @@ test.fixme( | |||||||
|       await helpMenuButton.click() |       await helpMenuButton.click() | ||||||
|       await restartOnboardingButton.click() |       await restartOnboardingButton.click() | ||||||
|  |  | ||||||
|       await nextButton.hover() |       await expect(restartConfirmationButton).toBeVisible() | ||||||
|       await nextButton.click() |       await restartConfirmationButton.click() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Confirm that the onboarding has restarted', async () => { |     await test.step('Confirm that the onboarding has restarted', async () => { | ||||||
|       await expect(tutorialProjectIndicator).toBeVisible() |       await expect(tutorialProjectIndicator).toBeVisible() | ||||||
|       await expect(tutorialModalText).toBeVisible() |       await expect(tutorialModalText).toBeVisible() | ||||||
|       // Make sure the model loaded |  | ||||||
|       const XYPlanePoint = { x: 988, y: 523 } as const |  | ||||||
|       const modelColor: [number, number, number] = [76, 76, 76] |  | ||||||
|  |  | ||||||
|       await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) |  | ||||||
|       await expectPixelColor(page, modelColor, XYPlanePoint, 8) |  | ||||||
|       await tutorialDismissButton.click() |       await tutorialDismissButton.click() | ||||||
|       // Make sure model still there. |  | ||||||
|       await expectPixelColor(page, modelColor, XYPlanePoint, 8) |  | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Clear code and restart onboarding from settings', async () => { |     await test.step('Clear code and restart onboarding from settings', async () => { | ||||||
| @ -535,9 +480,11 @@ test.fixme( | |||||||
|  |  | ||||||
|       await restartOnboardingSettingsButton.click() |       await restartOnboardingSettingsButton.click() | ||||||
|       // Since the code is empty, we should not see the confirmation dialog |       // Since the code is empty, we should not see the confirmation dialog | ||||||
|       await expect(nextButton).not.toBeVisible() |       await expect(restartConfirmationButton).not.toBeVisible() | ||||||
|       await expect(tutorialProjectIndicator).toBeVisible() |       await expect(tutorialProjectIndicator).toBeVisible() | ||||||
|       await expect(tutorialModalText).toBeVisible() |       await expect(tutorialModalText).toBeVisible() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     await electronApp.close() | ||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1,36 +1,20 @@ | |||||||
| import { test, expect, Page } from './zoo-test' | import { test, expect, AuthenticatedApp } from './fixtures/fixtureSetup' | ||||||
| import { EditorFixture } from './fixtures/editorFixture' | import { EditorFixture } from './fixtures/editorFixture' | ||||||
| import { SceneFixture } from './fixtures/sceneFixture' | import { SceneFixture } from './fixtures/sceneFixture' | ||||||
| import { ToolbarFixture } from './fixtures/toolbarFixture' | import { ToolbarFixture } from './fixtures/toolbarFixture' | ||||||
| import fs from 'node:fs/promises' |  | ||||||
| import path from 'node:path' |  | ||||||
| import { getUtils } from './test-utils' |  | ||||||
|  |  | ||||||
| // test file is for testing point an click code gen functionality that's not sketch mode related | // test file is for testing point an click code gen functionality that's not sketch mode related | ||||||
|  |  | ||||||
| test('verify extruding circle works', async ({ | test( | ||||||
|   context, |   'verify extruding circle works', | ||||||
|   homePage, |   { tag: ['@skipWin'] }, | ||||||
|   cmdBar, |   async ({ app, cmdBar, editor, toolbar, scene }) => { | ||||||
|   editor, |     test.skip( | ||||||
|   toolbar, |       process.platform === 'win32', | ||||||
|   scene, |       'Fails on windows in CI, can not be replicated locally on windows.' | ||||||
| }) => { |  | ||||||
|   // TODO: fix this test on windows after the electron migration |  | ||||||
|   test.skip(process.platform === 'win32', 'Skip on windows') |  | ||||||
|   const file = await fs.readFile( |  | ||||||
|     path.resolve( |  | ||||||
|       __dirname, |  | ||||||
|       '../../', |  | ||||||
|       './src/wasm-lib/tests/executor/inputs/test-circle-extrude.kcl' |  | ||||||
|     ), |  | ||||||
|     'utf-8' |  | ||||||
|     ) |     ) | ||||||
|   await context.addInitScript((file) => { |     const file = await app.getInputFile('test-circle-extrude.kcl') | ||||||
|     localStorage.setItem('persistCode', file) |     await app.initialise(file) | ||||||
|   }, file) |  | ||||||
|   await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217) |     const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217) | ||||||
|  |  | ||||||
|     await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => { |     await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => { | ||||||
| @ -43,17 +27,7 @@ test('verify extruding circle works', async ({ | |||||||
|       const circleSnippet = |       const circleSnippet = | ||||||
|         'circle({ center = [318.33, 168.1], radius = 182.8 }, %)' |         'circle({ center = [318.33, 168.1], radius = 182.8 }, %)' | ||||||
|       await editor.expectState({ |       await editor.expectState({ | ||||||
|       activeLines: ["constsketch002=startSketchOn('XZ')"], |         activeLines: [], | ||||||
|       highlightedCode: circleSnippet, |  | ||||||
|       diagnostics: [], |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await test.step('check code model connection works and that button is still enable once circle is selected ', async () => { |  | ||||||
|       await moveToCircle() |  | ||||||
|       const circleSnippet = |  | ||||||
|         'circle({ center = [318.33, 168.1], radius = 182.8 }, %)' |  | ||||||
|       await editor.expectState({ |  | ||||||
|         activeLines: ["constsketch002=startSketchOn('XZ')"], |  | ||||||
|         highlightedCode: circleSnippet, |         highlightedCode: circleSnippet, | ||||||
|         diagnostics: [], |         diagnostics: [], | ||||||
|       }) |       }) | ||||||
| @ -66,8 +40,6 @@ test('verify extruding circle works', async ({ | |||||||
|       }) |       }) | ||||||
|       await expect(toolbar.extrudeButton).toBeEnabled() |       await expect(toolbar.extrudeButton).toBeEnabled() | ||||||
|     }) |     }) | ||||||
|     await expect(toolbar.extrudeButton).toBeEnabled() |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|     await test.step('do extrude flow and check extrude code is added to editor', async () => { |     await test.step('do extrude flow and check extrude code is added to editor', async () => { | ||||||
|       await toolbar.extrudeButton.click() |       await toolbar.extrudeButton.click() | ||||||
| @ -94,14 +66,13 @@ test('verify extruding circle works', async ({ | |||||||
|  |  | ||||||
|       await editor.expectEditor.toContain(expectString) |       await editor.expectEditor.toContain(expectString) | ||||||
|     }) |     }) | ||||||
| }) |   } | ||||||
|  | ) | ||||||
|  |  | ||||||
| test.describe('verify sketch on chamfer works', () => { | test.describe('verify sketch on chamfer works', () => { | ||||||
|   // TODO: fix this test on windows after the electron migration |  | ||||||
|   test.skip(process.platform === 'win32', 'Skip on windows') |  | ||||||
|   const _sketchOnAChamfer = |   const _sketchOnAChamfer = | ||||||
|     ( |     ( | ||||||
|       page: Page, |       app: AuthenticatedApp, | ||||||
|       editor: EditorFixture, |       editor: EditorFixture, | ||||||
|       toolbar: ToolbarFixture, |       toolbar: ToolbarFixture, | ||||||
|       scene: SceneFixture |       scene: SceneFixture | ||||||
| @ -153,7 +124,7 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|         await toolbar.startSketchPlaneSelection() |         await toolbar.startSketchPlaneSelection() | ||||||
|         await clickChamfer() |         await clickChamfer() | ||||||
|         // timeout wait for engine animation is unavoidable |         // timeout wait for engine animation is unavoidable | ||||||
|         await page.waitForTimeout(1000) |         await app.page.waitForTimeout(600) | ||||||
|         await editor.expectEditor.toContain(afterChamferSelectSnippet) |         await editor.expectEditor.toContain(afterChamferSelectSnippet) | ||||||
|       }) |       }) | ||||||
|       await test.step('make sure a basic sketch can be added', async () => { |       await test.step('make sure a basic sketch can be added', async () => { | ||||||
| @ -164,9 +135,7 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|           pixelDiff: 50, |           pixelDiff: 50, | ||||||
|         }) |         }) | ||||||
|         await rectangle2ndClick() |         await rectangle2ndClick() | ||||||
|         await editor.expectEditor.toContain(afterRectangle2ndClickSnippet, { |         await editor.expectEditor.toContain(afterRectangle2ndClickSnippet) | ||||||
|           shouldNormalise: true, |  | ||||||
|         }) |  | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await test.step('Clean up so that `_sketchOnAChamfer` util can be called again', async () => { |       await test.step('Clean up so that `_sketchOnAChamfer` util can be called again', async () => { | ||||||
| @ -181,29 +150,18 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|         }) |         }) | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   test('works on all edge selections and can break up multi edges in a chamfer array', async ({ |   test( | ||||||
|     context, |     'works on all edge selections and can break up multi edges in a chamfer array', | ||||||
|     page, |     { tag: ['@skipWin'] }, | ||||||
|     homePage, |     async ({ app, editor, toolbar, scene }) => { | ||||||
|     editor, |       test.skip( | ||||||
|     toolbar, |         process.platform === 'win32', | ||||||
|     scene, |         'Fails on windows in CI, can not be replicated locally on windows.' | ||||||
|   }) => { |  | ||||||
|     const file = await fs.readFile( |  | ||||||
|       path.resolve( |  | ||||||
|         __dirname, |  | ||||||
|         '../../', |  | ||||||
|         './src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer.kcl' |  | ||||||
|       ), |  | ||||||
|       'utf-8' |  | ||||||
|       ) |       ) | ||||||
|     await context.addInitScript((file) => { |       const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl') | ||||||
|       localStorage.setItem('persistCode', file) |       await app.initialise(file) | ||||||
|     }, file) |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene) |       const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene) | ||||||
|  |  | ||||||
|       await sketchOnAChamfer({ |       await sketchOnAChamfer({ | ||||||
|         clickCoords: { x: 570, y: 220 }, |         clickCoords: { x: 570, y: 220 }, | ||||||
| @ -217,7 +175,8 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|       getOppositeEdge(seg01) |       getOppositeEdge(seg01) | ||||||
|     ]}, %)`, |     ]}, %)`, | ||||||
|  |  | ||||||
|       afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)', |         afterChamferSelectSnippet: | ||||||
|  |           'sketch002 = startSketchOn(extrude001, seg03)', | ||||||
|         afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', |         afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', | ||||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) |         afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||||
|     |> angledLine([ |     |> angledLine([ | ||||||
| @ -248,7 +207,8 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|          ] |          ] | ||||||
|        }, %)`, |        }, %)`, | ||||||
|  |  | ||||||
|       afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)', |         afterChamferSelectSnippet: | ||||||
|  |           'sketch003 = startSketchOn(extrude001, seg04)', | ||||||
|         afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', |         afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', | ||||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) |         afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||||
|     |> angledLine([ |     |> angledLine([ | ||||||
| @ -273,8 +233,9 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|            getNextAdjacentEdge(seg02) |            getNextAdjacentEdge(seg02) | ||||||
|          ] |          ] | ||||||
|        }, %)`, |        }, %)`, | ||||||
|       afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)', |         afterChamferSelectSnippet: | ||||||
|       afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)', |           'sketch003 = startSketchOn(extrude001, seg04)', | ||||||
|  |         afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', | ||||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) |         afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||||
|     |> angledLine([ |     |> angledLine([ | ||||||
|          segAng(rectangleSegmentA003) - 90, |          segAng(rectangleSegmentA003) - 90, | ||||||
| @ -296,7 +257,8 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|          length = 30, |          length = 30, | ||||||
|          tags = [getNextAdjacentEdge(yo)] |          tags = [getNextAdjacentEdge(yo)] | ||||||
|        }, %)`, |        }, %)`, | ||||||
|       afterChamferSelectSnippet: 'sketch005 = startSketchOn(extrude001, seg06)', |         afterChamferSelectSnippet: | ||||||
|  |           'sketch005 = startSketchOn(extrude001, seg06)', | ||||||
|         afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)', |         afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)', | ||||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005) |         afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005) | ||||||
|  |  | ||||||
| @ -398,31 +360,23 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|           { shouldNormalise: true } |           { shouldNormalise: true } | ||||||
|         ) |         ) | ||||||
|       }) |       }) | ||||||
|   }) |     } | ||||||
|  |  | ||||||
|   test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({ |  | ||||||
|     context, |  | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|     editor, |  | ||||||
|     toolbar, |  | ||||||
|     scene, |  | ||||||
|   }) => { |  | ||||||
|     const file = await fs.readFile( |  | ||||||
|       path.resolve( |  | ||||||
|         __dirname, |  | ||||||
|         '../../', |  | ||||||
|         './src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer-no-pipeExpr.kcl' |  | ||||||
|       ), |  | ||||||
|       'utf-8' |  | ||||||
|   ) |   ) | ||||||
|     await context.addInitScript((file) => { |  | ||||||
|       localStorage.setItem('persistCode', file) |  | ||||||
|     }, file) |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene) |   test( | ||||||
|  |     'Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', | ||||||
|  |     { tag: ['@skipWin'] }, | ||||||
|  |     async ({ app, editor, toolbar, scene }) => { | ||||||
|  |       test.skip( | ||||||
|  |         process.platform === 'win32', | ||||||
|  |         'Fails on windows in CI, can not be replicated locally on windows.' | ||||||
|  |       ) | ||||||
|  |       const file = await app.getInputFile( | ||||||
|  |         'e2e-can-sketch-on-chamfer-no-pipeExpr.kcl' | ||||||
|  |       ) | ||||||
|  |       await app.initialise(file) | ||||||
|  |  | ||||||
|  |       const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene) | ||||||
|  |  | ||||||
|       await sketchOnAChamfer({ |       await sketchOnAChamfer({ | ||||||
|         clickCoords: { x: 570, y: 220 }, |         clickCoords: { x: 570, y: 220 }, | ||||||
| @ -436,7 +390,8 @@ test.describe('verify sketch on chamfer works', () => { | |||||||
|       getOppositeEdge(seg01) |       getOppositeEdge(seg01) | ||||||
|     ]}, extrude001)`, |     ]}, extrude001)`, | ||||||
|         beforeChamferSnippetEnd: '}, extrude001)', |         beforeChamferSnippetEnd: '}, extrude001)', | ||||||
|       afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)', |         afterChamferSelectSnippet: | ||||||
|  |           'sketch002 = startSketchOn(extrude001, seg03)', | ||||||
|         afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', |         afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', | ||||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) |         afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||||
|     |> angledLine([ |     |> angledLine([ | ||||||
| @ -493,54 +448,48 @@ sketch002 = startSketchOn(extrude001, seg03) | |||||||
| `, | `, | ||||||
|         { shouldNormalise: true } |         { shouldNormalise: true } | ||||||
|       ) |       ) | ||||||
|   }) |     } | ||||||
|  |   ) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test(`Verify axis, origin, and horizontal snapping`, async ({ | test(`Verify axis, origin, and horizontal snapping`, async ({ | ||||||
|   page, |   app, | ||||||
|   homePage, |  | ||||||
|   editor, |   editor, | ||||||
|   toolbar, |   toolbar, | ||||||
|   scene, |   scene, | ||||||
| }) => { | }) => { | ||||||
|   const viewPortSize = { width: 1200, height: 500 } |  | ||||||
|  |  | ||||||
|   await page.setBodyDimensions(viewPortSize) |  | ||||||
|  |  | ||||||
|   await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|   // Constants and locators |   // Constants and locators | ||||||
|   // These are mappings from screenspace to KCL coordinates, |   // These are mappings from screenspace to KCL coordinates, | ||||||
|   // until we merge in our coordinate system helpers |   // until we merge in our coordinate system helpers | ||||||
|   const xzPlane = [ |   const xzPlane = [ | ||||||
|     viewPortSize.width * 0.65, |     app.viewPortSize.width * 0.65, | ||||||
|     viewPortSize.height * 0.3, |     app.viewPortSize.height * 0.3, | ||||||
|   ] as const |   ] as const | ||||||
|   const originSloppy = { |   const originSloppy = { | ||||||
|     screen: [ |     screen: [ | ||||||
|       viewPortSize.width / 2 + 3, // 3px off the center of the screen |       app.viewPortSize.width / 2 + 3, // 3px off the center of the screen | ||||||
|       viewPortSize.height / 2, |       app.viewPortSize.height / 2, | ||||||
|     ], |     ], | ||||||
|     kcl: [0, 0], |     kcl: [0, 0], | ||||||
|   } as const |   } as const | ||||||
|   const xAxisSloppy = { |   const xAxisSloppy = { | ||||||
|     screen: [ |     screen: [ | ||||||
|       viewPortSize.width * 0.75, |       app.viewPortSize.width * 0.75, | ||||||
|       viewPortSize.height / 2 - 3, // 3px off the X-axis |       app.viewPortSize.height / 2 - 3, // 3px off the X-axis | ||||||
|     ], |     ], | ||||||
|     kcl: [20.34, 0], |     kcl: [16.95, 0], | ||||||
|   } as const |   } as const | ||||||
|   const offYAxis = { |   const offYAxis = { | ||||||
|     screen: [ |     screen: [ | ||||||
|       viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range |       app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range | ||||||
|       viewPortSize.height * 0.3, |       app.viewPortSize.height * 0.3, | ||||||
|     ], |     ], | ||||||
|     kcl: [8.14, 6.78], |     kcl: [6.78, 6.78], | ||||||
|   } as const |   } as const | ||||||
|   const yAxisSloppy = { |   const yAxisSloppy = { | ||||||
|     screen: [ |     screen: [ | ||||||
|       viewPortSize.width / 2 + 5, // 5px off the Y-axis |       app.viewPortSize.width / 2 + 5, // 5px off the Y-axis | ||||||
|       viewPortSize.height * 0.3, |       app.viewPortSize.height * 0.3, | ||||||
|     ], |     ], | ||||||
|     kcl: [0, 6.78], |     kcl: [0, 6.78], | ||||||
|   } as const |   } as const | ||||||
| @ -561,13 +510,15 @@ test(`Verify axis, origin, and horizontal snapping`, async ({ | |||||||
|     afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`, |     afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`, | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   await app.initialise() | ||||||
|  |  | ||||||
|   await test.step(`Start a sketch on the XZ plane`, async () => { |   await test.step(`Start a sketch on the XZ plane`, async () => { | ||||||
|     await editor.closePane() |     await editor.closePane() | ||||||
|     await toolbar.startSketchPlaneSelection() |     await toolbar.startSketchPlaneSelection() | ||||||
|     await moveToXzPlane() |     await moveToXzPlane() | ||||||
|     await clickOnXzPlane() |     await clickOnXzPlane() | ||||||
|     // timeout wait for engine animation is unavoidable |     // timeout wait for engine animation is unavoidable | ||||||
|     await page.waitForTimeout(600) |     await app.page.waitForTimeout(600) | ||||||
|     await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane) |     await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane) | ||||||
|   }) |   }) | ||||||
|   await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => { |   await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => { | ||||||
| @ -602,15 +553,11 @@ test(`Verify axis, origin, and horizontal snapping`, async ({ | |||||||
| }) | }) | ||||||
|  |  | ||||||
| test(`Verify user can double-click to edit a sketch`, async ({ | test(`Verify user can double-click to edit a sketch`, async ({ | ||||||
|   context, |   app, | ||||||
|   page, |  | ||||||
|   homePage, |  | ||||||
|   editor, |   editor, | ||||||
|   toolbar, |   toolbar, | ||||||
|   scene, |   scene, | ||||||
| }) => { | }) => { | ||||||
|   const u = await getUtils(page) |  | ||||||
|  |  | ||||||
|   const initialCode = `closedSketch = startSketchOn('XZ') |   const initialCode = `closedSketch = startSketchOn('XZ') | ||||||
|   |> circle({ center = [8, 5], radius = 2 }, %) |   |> circle({ center = [8, 5], radius = 2 }, %) | ||||||
| openSketch = startSketchOn('XY') | openSketch = startSketchOn('XY') | ||||||
| @ -619,24 +566,15 @@ openSketch = startSketchOn('XY') | |||||||
|   |> xLine(5, %) |   |> xLine(5, %) | ||||||
|   |> tangentialArcTo([10, 0], %) |   |> tangentialArcTo([10, 0], %) | ||||||
| ` | ` | ||||||
|   const viewPortSize = { width: 1000, height: 500 } |   await app.initialise(initialCode) | ||||||
|   await page.setBodyDimensions(viewPortSize) |  | ||||||
|  |  | ||||||
|   await context.addInitScript((code) => { |  | ||||||
|     localStorage.setItem('persistCode', code) |  | ||||||
|   }, initialCode) |  | ||||||
|  |  | ||||||
|   await homePage.goToModelingScene() |  | ||||||
|   await u.waitForPageLoad() |  | ||||||
|   await page.waitForTimeout(1000) |  | ||||||
|  |  | ||||||
|   const pointInsideCircle = { |   const pointInsideCircle = { | ||||||
|     x: viewPortSize.width * 0.63, |     x: app.viewPortSize.width * 0.63, | ||||||
|     y: viewPortSize.height * 0.5, |     y: app.viewPortSize.height * 0.5, | ||||||
|   } |   } | ||||||
|   const pointOnPathAfterSketching = { |   const pointOnPathAfterSketching = { | ||||||
|     x: viewPortSize.width * 0.65, |     x: app.viewPortSize.width * 0.58, | ||||||
|     y: viewPortSize.height * 0.5, |     y: app.viewPortSize.height * 0.5, | ||||||
|   } |   } | ||||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars |   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||||
|   const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] = |   const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] = | ||||||
| @ -669,59 +607,41 @@ openSketch = startSketchOn('XY') | |||||||
|       diagnostics: [], |       diagnostics: [], | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|   await page.waitForTimeout(1000) |  | ||||||
|  |  | ||||||
|   await exitSketch() |   await exitSketch() | ||||||
|   await page.waitForTimeout(1000) |  | ||||||
|  |  | ||||||
|   // Drag the sketch line out of the axis view which blocks the click |  | ||||||
|   await page.dragAndDrop('#stream', '#stream', { |  | ||||||
|     sourcePosition: { |  | ||||||
|       x: viewPortSize.width * 0.7, |  | ||||||
|       y: viewPortSize.height * 0.5, |  | ||||||
|     }, |  | ||||||
|     targetPosition: { |  | ||||||
|       x: viewPortSize.width * 0.7, |  | ||||||
|       y: viewPortSize.height * 0.4, |  | ||||||
|     }, |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   await page.waitForTimeout(500) |  | ||||||
|  |  | ||||||
|   await test.step(`Double-click on the open sketch`, async () => { |   await test.step(`Double-click on the open sketch`, async () => { | ||||||
|     await moveToOpenPath() |     await moveToOpenPath() | ||||||
|     await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15) |     await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15) | ||||||
|     // There is a full execution after exiting sketch that clears the scene. |     // There is a full execution after exiting sketch that clears the scene. | ||||||
|     await page.waitForTimeout(500) |     await app.page.waitForTimeout(500) | ||||||
|     await dblClickOpenPath() |     await dblClickOpenPath() | ||||||
|     await expect(toolbar.startSketchBtn).not.toBeVisible() |     await expect(toolbar.startSketchBtn).not.toBeVisible() | ||||||
|     await expect(toolbar.exitSketchBtn).toBeVisible() |     await expect(toolbar.exitSketchBtn).toBeVisible() | ||||||
|     // Wait for enter sketch mode to complete |     // Wait for enter sketch mode to complete | ||||||
|     await page.waitForTimeout(500) |     await app.page.waitForTimeout(500) | ||||||
|     await editor.expectState({ |     await editor.expectState({ | ||||||
|       activeLines: [`|>tangentialArcTo([10,0],%)`], |       activeLines: [`|>xLine(5,%)`], | ||||||
|       highlightedCode: 'tangentialArcTo([10,0],%)', |       highlightedCode: 'xLine(5,%)', | ||||||
|       diagnostics: [], |       diagnostics: [], | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test(`Offset plane point-and-click`, async ({ | test(`Offset plane point-and-click`, async ({ | ||||||
|   context, |   app, | ||||||
|   page, |  | ||||||
|   homePage, |  | ||||||
|   scene, |   scene, | ||||||
|   editor, |   editor, | ||||||
|   toolbar, |   toolbar, | ||||||
|   cmdBar, |   cmdBar, | ||||||
| }) => { | }) => { | ||||||
|  |   await app.initialise() | ||||||
|  |  | ||||||
|   // One dumb hardcoded screen pixel value |   // One dumb hardcoded screen pixel value | ||||||
|   const testPoint = { x: 700, y: 150 } |   const testPoint = { x: 700, y: 150 } | ||||||
|   const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y) |   const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||||
|   const expectedOutput = `plane001 = offsetPlane('XZ', 5)` |   const expectedOutput = `plane001 = offsetPlane('XZ', 5)` | ||||||
|  |  | ||||||
|   await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|   await test.step(`Look for the blue of the XZ plane`, async () => { |   await test.step(`Look for the blue of the XZ plane`, async () => { | ||||||
|     await scene.expectPixelColor([50, 51, 96], testPoint, 15) |     await scene.expectPixelColor([50, 51, 96], testPoint, 15) | ||||||
|   }) |   }) | ||||||
| @ -764,9 +684,8 @@ const loftPointAndClickCases = [ | |||||||
| ] | ] | ||||||
| loftPointAndClickCases.forEach(({ shouldPreselect }) => { | loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||||
|   test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({ |   test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({ | ||||||
|     context, |     app, | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|     scene, |     scene, | ||||||
|     editor, |     editor, | ||||||
|     toolbar, |     toolbar, | ||||||
| @ -778,11 +697,7 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => { | |||||||
|     sketch002 = startSketchOn(plane001) |     sketch002 = startSketchOn(plane001) | ||||||
|     |> circle({ center = [0, 0], radius = 20 }, %) |     |> circle({ center = [0, 0], radius = 20 }, %) | ||||||
| ` | ` | ||||||
|     await context.addInitScript((initialCode) => { |     await app.initialise(initialCode) | ||||||
|       localStorage.setItem('persistCode', initialCode) |  | ||||||
|     }, initialCode) |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     // One dumb hardcoded screen pixel value |     // One dumb hardcoded screen pixel value | ||||||
|     const testPoint = { x: 575, y: 200 } |     const testPoint = { x: 575, y: 200 } | ||||||
| @ -801,7 +716,7 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => { | |||||||
|       await clickOnSketch1() |       await clickOnSketch1() | ||||||
|       await page.keyboard.down('Shift') |       await page.keyboard.down('Shift') | ||||||
|       await clickOnSketch2() |       await clickOnSketch2() | ||||||
|       await page.waitForTimeout(500) |       await app.page.waitForTimeout(500) | ||||||
|       await page.keyboard.up('Shift') |       await page.keyboard.up('Shift') | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -860,25 +775,17 @@ const shellPointAndClickCapCases = [ | |||||||
| ] | ] | ||||||
| shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | ||||||
|   test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({ |   test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({ | ||||||
|     context, |     app, | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|     scene, |     scene, | ||||||
|     editor, |     editor, | ||||||
|     toolbar, |     toolbar, | ||||||
|     cmdBar, |     cmdBar, | ||||||
|   }) => { |   }) => { | ||||||
|     // TODO: fix this test on windows after the electron migration |  | ||||||
|     test.skip(process.platform === 'win32', 'Skip on windows') |  | ||||||
|     const initialCode = `sketch001 = startSketchOn('XZ') |     const initialCode = `sketch001 = startSketchOn('XZ') | ||||||
|     |> circle({ center = [0, 0], radius = 30 }, %) |     |> circle({ center = [0, 0], radius = 30 }, %) | ||||||
|     extrude001 = extrude(30, sketch001) |     extrude001 = extrude(30, sketch001) | ||||||
|     ` |     ` | ||||||
|     await context.addInitScript((initialCode) => { |     await app.initialise(initialCode) | ||||||
|       localStorage.setItem('persistCode', initialCode) |  | ||||||
|     }, initialCode) |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     // One dumb hardcoded screen pixel value |     // One dumb hardcoded screen pixel value | ||||||
|     const testPoint = { x: 575, y: 200 } |     const testPoint = { x: 575, y: 200 } | ||||||
| @ -905,7 +812,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | |||||||
|           commandName: 'Shell', |           commandName: 'Shell', | ||||||
|         }) |         }) | ||||||
|         await clickOnCap() |         await clickOnCap() | ||||||
|         await page.waitForTimeout(500) |         await app.page.waitForTimeout(500) | ||||||
|         await cmdBar.progressCmdBar() |         await cmdBar.progressCmdBar() | ||||||
|         await cmdBar.progressCmdBar() |         await cmdBar.progressCmdBar() | ||||||
|         await cmdBar.expectState({ |         await cmdBar.expectState({ | ||||||
| @ -921,7 +828,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | |||||||
|     } else { |     } else { | ||||||
|       await test.step(`Preselect the cap`, async () => { |       await test.step(`Preselect the cap`, async () => { | ||||||
|         await clickOnCap() |         await clickOnCap() | ||||||
|         await page.waitForTimeout(500) |         await app.page.waitForTimeout(500) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { |       await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { | ||||||
| @ -953,9 +860,8 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { | |||||||
| }) | }) | ||||||
|  |  | ||||||
| test('Shell point-and-click wall', async ({ | test('Shell point-and-click wall', async ({ | ||||||
|   context, |   app, | ||||||
|   page, |   page, | ||||||
|   homePage, |  | ||||||
|   scene, |   scene, | ||||||
|   editor, |   editor, | ||||||
|   toolbar, |   toolbar, | ||||||
| @ -970,11 +876,7 @@ test('Shell point-and-click wall', async ({ | |||||||
|   |> close(%) |   |> close(%) | ||||||
| extrude001 = extrude(40, sketch001) | extrude001 = extrude(40, sketch001) | ||||||
|   ` |   ` | ||||||
|   await context.addInitScript((initialCode) => { |   await app.initialise(initialCode) | ||||||
|     localStorage.setItem('persistCode', initialCode) |  | ||||||
|   }, initialCode) |  | ||||||
|   await page.setBodyDimensions({ width: 1000, height: 500 }) |  | ||||||
|   await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|   // One dumb hardcoded screen pixel value |   // One dumb hardcoded screen pixel value | ||||||
|   const testPoint = { x: 580, y: 180 } |   const testPoint = { x: 580, y: 180 } | ||||||
| @ -1005,7 +907,7 @@ extrude001 = extrude(40, sketch001) | |||||||
|     await clickOnCap() |     await clickOnCap() | ||||||
|     await page.keyboard.down('Shift') |     await page.keyboard.down('Shift') | ||||||
|     await clickOnWall() |     await clickOnWall() | ||||||
|     await page.waitForTimeout(500) |     await app.page.waitForTimeout(500) | ||||||
|     await page.keyboard.up('Shift') |     await page.keyboard.up('Shift') | ||||||
|     await cmdBar.progressCmdBar() |     await cmdBar.progressCmdBar() | ||||||
|     await cmdBar.progressCmdBar() |     await cmdBar.progressCmdBar() | ||||||
|  | |||||||
| @ -1,190 +0,0 @@ | |||||||
| import { test, expect } from './zoo-test' |  | ||||||
|  |  | ||||||
| /* eslint-disable jest/no-conditional-expect */ |  | ||||||
|  |  | ||||||
| const file = `sketch001 = startSketchOn('XZ') |  | ||||||
| profile001 = startProfileAt([57.81, 250.51], sketch001) |  | ||||||
|   |> line([121.13, 56.63], %, $seg02) |  | ||||||
|   |> line([83.37, -34.61], %, $seg01) |  | ||||||
|   |> line([19.66, -116.4], %) |  | ||||||
|   |> line([-221.8, -41.69], %) |  | ||||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) |  | ||||||
|   |> close(%) |  | ||||||
| extrude001 = extrude(200, profile001) |  | ||||||
| sketch002 = startSketchOn('XZ') |  | ||||||
|   |> startProfileAt([-73.64, -42.89], %) |  | ||||||
|   |> xLine(173.71, %) |  | ||||||
|   |> line([-22.12, -94.4], %) |  | ||||||
|   |> xLine(-156.98, %) |  | ||||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) |  | ||||||
|   |> close(%) |  | ||||||
| extrude002 = extrude(50, sketch002) |  | ||||||
| sketch003 = startSketchOn('XY') |  | ||||||
|   |> startProfileAt([52.92, 157.81], %) |  | ||||||
|   |> angledLine([0, 176.4], %, $rectangleSegmentA001) |  | ||||||
|   |> angledLine([ |  | ||||||
|        segAng(rectangleSegmentA001) - 90, |  | ||||||
|        53.4 |  | ||||||
|      ], %, $rectangleSegmentB001) |  | ||||||
|   |> angledLine([ |  | ||||||
|        segAng(rectangleSegmentA001), |  | ||||||
|        -segLen(rectangleSegmentA001) |  | ||||||
|      ], %, $rectangleSegmentC001) |  | ||||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) |  | ||||||
|   |> close(%) |  | ||||||
| extrude003 = extrude(20, sketch003) |  | ||||||
| ` |  | ||||||
|  |  | ||||||
| test.describe('Check the happy path, for basic changing color', () => { |  | ||||||
|   const cases = [ |  | ||||||
|     { |  | ||||||
|       desc: 'User accepts change', |  | ||||||
|       shouldReject: false, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       desc: 'User rejects change', |  | ||||||
|       shouldReject: true, |  | ||||||
|     }, |  | ||||||
|   ] as const |  | ||||||
|   for (const { desc, shouldReject } of cases) { |  | ||||||
|     test(`${desc}`, async ({ |  | ||||||
|       context, |  | ||||||
|       homePage, |  | ||||||
|       cmdBar, |  | ||||||
|       editor, |  | ||||||
|       page, |  | ||||||
|       scene, |  | ||||||
|     }) => { |  | ||||||
|       await context.addInitScript((file) => { |  | ||||||
|         localStorage.setItem('persistCode', file) |  | ||||||
|       }, file) |  | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|       const body1CapCoords = { x: 571, y: 351 } |  | ||||||
|       const greenCheckCoords = { x: 565, y: 345 } |  | ||||||
|       const body2WallCoords = { x: 609, y: 153 } |  | ||||||
|       const [clickBody1Cap] = scene.makeMouseHelpers( |  | ||||||
|         body1CapCoords.x, |  | ||||||
|         body1CapCoords.y |  | ||||||
|       ) |  | ||||||
|       const yellow: [number, number, number] = [179, 179, 131] |  | ||||||
|       const green: [number, number, number] = [108, 152, 75] |  | ||||||
|       const notGreen: [number, number, number] = [132, 132, 132] |  | ||||||
|       const body2NotGreen: [number, number, number] = [88, 88, 88] |  | ||||||
|       const submittingToast = page.getByText('Submitting to Text-to-CAD API...') |  | ||||||
|       const successToast = page.getByText('Prompt to edit successful') |  | ||||||
|       const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' }) |  | ||||||
|       const rejectBtn = page.getByRole('button', { name: 'close Reject' }) |  | ||||||
|  |  | ||||||
|       await test.step('wait for scene to load select body and check selection came through', async () => { |  | ||||||
|         await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15) |  | ||||||
|         await clickBody1Cap() |  | ||||||
|         await scene.expectPixelColor(yellow, body1CapCoords, 20) |  | ||||||
|         await editor.expectState({ |  | ||||||
|           highlightedCode: '', |  | ||||||
|           activeLines: ['|>startProfileAt([-73.64,-42.89],%)'], |  | ||||||
|           diagnostics: [], |  | ||||||
|         }) |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|       await test.step('fire off edit prompt', async () => { |  | ||||||
|         await cmdBar.openCmdBar('promptToEdit') |  | ||||||
|         // being specific about the color with a hex means asserting pixel color is more stable |  | ||||||
|         await page |  | ||||||
|           .getByTestId('cmd-bar-arg-value') |  | ||||||
|           .fill('make this neon green please, use #39FF14') |  | ||||||
|         await page.waitForTimeout(100) |  | ||||||
|         await cmdBar.progressCmdBar() |  | ||||||
|         await expect(submittingToast).toBeVisible() |  | ||||||
|         await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while |  | ||||||
|         await expect(successToast).toBeVisible() |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|       await test.step('verify initial change', async () => { |  | ||||||
|         await scene.expectPixelColor(green, greenCheckCoords, 15) |  | ||||||
|         await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15) |  | ||||||
|         await editor.expectEditor.toContain('appearance({') |  | ||||||
|       }) |  | ||||||
|  |  | ||||||
|       if (!shouldReject) { |  | ||||||
|         await test.step('check accept works and can be "undo"ed', async () => { |  | ||||||
|           await acceptBtn.click() |  | ||||||
|           await expect(successToast).not.toBeVisible() |  | ||||||
|  |  | ||||||
|           await scene.expectPixelColor(green, greenCheckCoords, 15) |  | ||||||
|           await editor.expectEditor.toContain('appearance({') |  | ||||||
|  |  | ||||||
|           // ctrl-z works after accepting |  | ||||||
|           await page.keyboard.down('ControlOrMeta') |  | ||||||
|           await page.keyboard.press('KeyZ') |  | ||||||
|           await page.keyboard.up('ControlOrMeta') |  | ||||||
|           await editor.expectEditor.not.toContain('appearance({') |  | ||||||
|           await scene.expectPixelColor(notGreen, greenCheckCoords, 15) |  | ||||||
|         }) |  | ||||||
|       } else { |  | ||||||
|         await test.step('check reject works', async () => { |  | ||||||
|           await rejectBtn.click() |  | ||||||
|           await expect(successToast).not.toBeVisible() |  | ||||||
|  |  | ||||||
|           await scene.expectPixelColor(notGreen, greenCheckCoords, 15) |  | ||||||
|           await editor.expectEditor.not.toContain('appearance({') |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| }) |  | ||||||
|  |  | ||||||
| test.describe('bad path', () => { |  | ||||||
|   test(`bad edit prompt`, async ({ |  | ||||||
|     context, |  | ||||||
|     homePage, |  | ||||||
|     cmdBar, |  | ||||||
|     editor, |  | ||||||
|     toolbar, |  | ||||||
|     page, |  | ||||||
|     scene, |  | ||||||
|   }) => { |  | ||||||
|     await context.addInitScript((file) => { |  | ||||||
|       localStorage.setItem('persistCode', file) |  | ||||||
|     }, file) |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|     const body1CapCoords = { x: 571, y: 351 } |  | ||||||
|     const [clickBody1Cap] = scene.makeMouseHelpers( |  | ||||||
|       body1CapCoords.x, |  | ||||||
|       body1CapCoords.y |  | ||||||
|     ) |  | ||||||
|     const yellow: [number, number, number] = [179, 179, 131] |  | ||||||
|     const submittingToast = page.getByText('Submitting to Text-to-CAD API...') |  | ||||||
|     const failToast = page.getByText( |  | ||||||
|       'Failed to edit your KCL code, please try again with a different prompt or selection' |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     await test.step('wait for scene to load and select body', async () => { |  | ||||||
|       await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15) |  | ||||||
|  |  | ||||||
|       await clickBody1Cap() |  | ||||||
|       await scene.expectPixelColor(yellow, body1CapCoords, 20) |  | ||||||
|  |  | ||||||
|       await editor.expectState({ |  | ||||||
|         highlightedCode: '', |  | ||||||
|         activeLines: ['|>startProfileAt([-73.64,-42.89],%)'], |  | ||||||
|         diagnostics: [], |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await test.step('fire of bad prompt', async () => { |  | ||||||
|       await cmdBar.openCmdBar('promptToEdit') |  | ||||||
|       await page |  | ||||||
|         .getByTestId('cmd-bar-arg-value') |  | ||||||
|         .fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak') |  | ||||||
|       await page.waitForTimeout(100) |  | ||||||
|       await cmdBar.progressCmdBar() |  | ||||||
|       await expect(submittingToast).toBeVisible() |  | ||||||
|     }) |  | ||||||
|     await test.step('check fail toast appeared', async () => { |  | ||||||
|       await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while |  | ||||||
|       await expect(failToast).toBeVisible() |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
| @ -1,22 +1,32 @@ | |||||||
| import { test, expect, Page } from './zoo-test' | import { test, expect, Page } from '@playwright/test' | ||||||
| import path from 'path' | import { join } from 'path' | ||||||
| import * as fsp from 'fs/promises' | import * as fsp from 'fs/promises' | ||||||
| import { getUtils, executorInputPath } from './test-utils' | import { | ||||||
|  |   getUtils, | ||||||
|  |   setup, | ||||||
|  |   setupElectron, | ||||||
|  |   tearDown, | ||||||
|  |   executorInputPath, | ||||||
|  | } from './test-utils' | ||||||
| import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates' | import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates' | ||||||
| import { bracket } from 'lib/exampleKcl' | import { bracket } from 'lib/exampleKcl' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Regression tests', () => { | test.describe('Regression tests', () => { | ||||||
|   // bugs we found that don't fit neatly into other categories |   // bugs we found that don't fit neatly into other categories | ||||||
|   test('bad model has inline error #3251', async ({ |   test('bad model has inline error #3251', async ({ page }) => { | ||||||
|     context, |  | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     // because the model has `line([0,0]..` it is valid code, but the model is invalid |     // because the model has `line([0,0]..` it is valid code, but the model is invalid | ||||||
|     // regression test for https://github.com/KittyCAD/modeling-app/issues/3251 |     // regression test for https://github.com/KittyCAD/modeling-app/issues/3251 | ||||||
|     // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics |     // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await context.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|         'persistCode', |         'persistCode', | ||||||
|         `sketch2 = startSketchOn("XY") |         `sketch2 = startSketchOn("XY") | ||||||
| @ -28,10 +38,9 @@ test.describe('Regression tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     // error in guter |     // error in guter | ||||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() |     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||||
| @ -47,7 +56,6 @@ test.describe('Regression tests', () => { | |||||||
|   }) |   }) | ||||||
|   test('user should not have to press down twice in cmdbar', async ({ |   test('user should not have to press down twice in cmdbar', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     // because the model has `line([0,0]..` it is valid code, but the model is invalid |     // because the model has `line([0,0]..` it is valid code, but the model is invalid | ||||||
|     // regression test for https://github.com/KittyCAD/modeling-app/issues/3251 |     // regression test for https://github.com/KittyCAD/modeling-app/issues/3251 | ||||||
| @ -56,38 +64,26 @@ test.describe('Regression tests', () => { | |||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
|         'persistCode', |         'persistCode', | ||||||
|         `sketch001 = startSketchOn('XY') |         `sketch2 = startSketchOn("XY") | ||||||
|   |> startProfileAt([82.33, 238.21], %) | sketch001 = startSketchAt([-0, -0]) | ||||||
|   |> angledLine([0, 288.63], %, $rectangleSegmentA001) |   |> line([0, 0], %) | ||||||
|   |> angledLine([ |   |> line([-4.84, -5.29], %) | ||||||
|        segAng(rectangleSegmentA001) - 90, |  | ||||||
|        197.97 |  | ||||||
|      ], %, $rectangleSegmentB001) |  | ||||||
|   |> angledLine([ |  | ||||||
|        segAng(rectangleSegmentA001), |  | ||||||
|        -segLen(rectangleSegmentA001) |  | ||||||
|      ], %, $rectangleSegmentC001) |  | ||||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) |   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||||
|   |> close(%) |   |> close(%)` | ||||||
| extrude001 = extrude(50, sketch001) |  | ||||||
| ` |  | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await page.goto('/') | ||||||
|     await u.waitForPageLoad() |     await u.waitForPageLoad() | ||||||
|  |  | ||||||
|     await test.step('Check arrow down works', async () => { |     await test.step('Check arrow down works', async () => { | ||||||
|       await page.getByTestId('command-bar-open-button').hover() |  | ||||||
|       await page.getByTestId('command-bar-open-button').click() |       await page.getByTestId('command-bar-open-button').click() | ||||||
|  |  | ||||||
|       const floppy = page.getByRole('option', { |       await page | ||||||
|         name: 'floppy disk arrow Export', |         .getByRole('option', { name: 'floppy disk arrow Export' }) | ||||||
|       }) |         .click() | ||||||
|  |  | ||||||
|       await floppy.click() |  | ||||||
|  |  | ||||||
|       // press arrow down key twice |       // press arrow down key twice | ||||||
|       await page.keyboard.press('ArrowDown') |       await page.keyboard.press('ArrowDown') | ||||||
| @ -119,7 +115,7 @@ extrude001 = extrude(50, sketch001) | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|   test('executes on load', async ({ page, homePage }) => { |   test('executes on load', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -131,10 +127,9 @@ extrude001 = extrude(50, sketch001) | |||||||
|     |> line([-23.44, 0.52], %)` |     |> line([-23.44, 0.52], %)` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     // expand variables section |     // expand variables section | ||||||
|     const variablesTabButton = page.getByTestId('variables-pane-button') |     const variablesTabButton = page.getByTestId('variables-pane-button') | ||||||
| @ -153,15 +148,14 @@ extrude001 = extrude(50, sketch001) | |||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('re-executes', async ({ page, homePage }) => { |   test('re-executes', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem('persistCode', `myVar = 5`) |       localStorage.setItem('persistCode', `myVar = 5`) | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     const variablesTabButton = page.getByTestId('variables-pane-button') |     const variablesTabButton = page.getByTestId('variables-pane-button') | ||||||
|     await variablesTabButton.click() |     await variablesTabButton.click() | ||||||
| @ -180,7 +174,7 @@ extrude001 = extrude(50, sketch001) | |||||||
|       page.locator('.pretty-json-container >> text=myVar:67') |       page.locator('.pretty-json-container >> text=myVar:67') | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|   }) |   }) | ||||||
|   test('ProgramMemory can be serialised', async ({ page, homePage }) => { |   test('ProgramMemory can be serialised', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -199,14 +193,13 @@ extrude001 = extrude(50, sketch001) | |||||||
|         }, %)` |         }, %)` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     const messages: string[] = [] |     const messages: string[] = [] | ||||||
|  |  | ||||||
|     // Listen for all console events and push the message text to an array |     // Listen for all console events and push the message text to an array | ||||||
|     page.on('console', (message) => messages.push(message.text())) |     page.on('console', (message) => messages.push(message.text())) | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -219,26 +212,19 @@ extrude001 = extrude(50, sketch001) | |||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |   test('ensure the Zoo logo is not a link in browser app', async ({ page }) => { | ||||||
|   // Not relevant to us anymore, or at least for the time being. |  | ||||||
|   test.skip('ensure the Zoo logo is not a link in browser app', async ({ |  | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     const zooLogo = page.locator('[data-testid="app-logo"]') |     const zooLogo = page.locator('[data-testid="app-logo"]') | ||||||
|     // Make sure it's not a link |     // Make sure it's not a link | ||||||
|     await expect(zooLogo).not.toHaveAttribute('href') |     await expect(zooLogo).not.toHaveAttribute('href') | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test( |   test( | ||||||
|     'Position _ Is Out Of Range... regression test', |     'Position _ Is Out Of Range... regression test', | ||||||
|     { tag: ['@skipWin'] }, |     { tag: ['@skipWin'] }, | ||||||
|     async ({ context, page, homePage }) => { |     async ({ page }) => { | ||||||
|       // SKip on windows, its being weird. |       // SKip on windows, its being weird. | ||||||
|       test.skip( |       test.skip( | ||||||
|         process.platform === 'win32', |         process.platform === 'win32', | ||||||
| @ -247,8 +233,8 @@ extrude001 = extrude(50, sketch001) | |||||||
|  |  | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio |       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       await context.addInitScript(async () => { |       await page.addInitScript(async () => { | ||||||
|         localStorage.setItem( |         localStorage.setItem( | ||||||
|           'persistCode', |           'persistCode', | ||||||
|           `exampleSketch = startSketchOn("XZ") |           `exampleSketch = startSketchOn("XZ") | ||||||
| @ -264,9 +250,8 @@ extrude001 = extrude(50, sketch001) | |||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await expect(async () => { |       await expect(async () => { | ||||||
|         await homePage.goToModelingScene() |         await page.goto('/') | ||||||
|         await u.waitForPageLoad() |         await u.waitForPageLoad() | ||||||
|  |  | ||||||
|         // error in guter |         // error in guter | ||||||
|         await expect(page.locator('.cm-lint-marker-error')).toBeVisible({ |         await expect(page.locator('.cm-lint-marker-error')).toBeVisible({ | ||||||
|           timeout: 1_000, |           timeout: 1_000, | ||||||
| @ -321,7 +306,6 @@ extrude001 = extrude(50, sketch001) | |||||||
|  |  | ||||||
|   test('when engine fails export we handle the failure and alert the user', async ({ |   test('when engine fails export we handle the failure and alert the user', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript( |     await page.addInitScript( | ||||||
| @ -332,10 +316,9 @@ extrude001 = extrude(50, sketch001) | |||||||
|       { code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } |       { code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) |     await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -391,6 +374,7 @@ extrude001 = extrude(50, sketch001) | |||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|  |     await u.clearCommandLogs() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|     await u.closeDebugPanel() |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
| @ -424,7 +408,7 @@ extrude001 = extrude(50, sketch001) | |||||||
|   test( |   test( | ||||||
|     'ensure you can not export while an export is already going', |     'ensure you can not export while an export is already going', | ||||||
|     { tag: ['@skipLinux', '@skipWin'] }, |     { tag: ['@skipLinux', '@skipWin'] }, | ||||||
|     async ({ page, homePage }) => { |     async ({ page }) => { | ||||||
|       // This is being weird on ubuntu and windows. |       // This is being weird on ubuntu and windows. | ||||||
|       test.skip( |       test.skip( | ||||||
|         // eslint-disable-next-line jest/valid-title |         // eslint-disable-next-line jest/valid-title | ||||||
| @ -444,10 +428,9 @@ extrude001 = extrude(50, sketch001) | |||||||
|           } |           } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         await page.setBodyDimensions({ width: 1000, height: 500 }) |         await page.setViewportSize({ width: 1000, height: 500 }) | ||||||
|  |  | ||||||
|         await homePage.goToModelingScene() |         await u.waitForAuthSkipAppStart() | ||||||
|         await u.waitForPageLoad() |  | ||||||
|  |  | ||||||
|         // wait for execution done |         // wait for execution done | ||||||
|         await u.openDebugPanel() |         await u.openDebugPanel() | ||||||
| @ -517,17 +500,20 @@ extrude001 = extrude(50, sketch001) | |||||||
|   test( |   test( | ||||||
|     `Network health indicator only appears in modeling view`, |     `Network health indicator only appears in modeling view`, | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ context, page }, testInfo) => { |     async ({ browserName: _ }, testInfo) => { | ||||||
|       await context.folderSetupFn(async (dir) => { |       const { electronApp, page } = await setupElectron({ | ||||||
|         const bracketDir = path.join(dir, 'bracket') |         testInfo, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|  |           const bracketDir = join(dir, 'bracket') | ||||||
|           await fsp.mkdir(bracketDir, { recursive: true }) |           await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|           await fsp.copyFile( |           await fsp.copyFile( | ||||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), |             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
|           path.join(bracketDir, 'main.kcl') |             join(bracketDir, 'main.kcl') | ||||||
|           ) |           ) | ||||||
|  |         }, | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|  |  | ||||||
|       // Locators |       // Locators | ||||||
| @ -553,19 +539,18 @@ extrude001 = extrude(50, sketch001) | |||||||
|         await u.waitForPageLoad() |         await u.waitForPageLoad() | ||||||
|         await expect(networkHealthIndicator).toContainText('Connected') |         await expect(networkHealthIndicator).toContainText('Connected') | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   test(`View gizmo stays visible even when zoomed out all the way`, async ({ |   test(`View gizmo stays visible even when zoomed out all the way`, async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     // TODO: fix this test on windows after the electron migration |  | ||||||
|     test.skip(process.platform === 'win32', 'Skip on windows') |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     // Constants and locators |     // Constants and locators | ||||||
|     const planeColor: [number, number, number] = [170, 220, 170] |     const planeColor: [number, number, number] = [161, 220, 155] | ||||||
|     const bgColor: [number, number, number] = [27, 27, 27] |     const bgColor: [number, number, number] = [27, 27, 27] | ||||||
|     const middlePixelIsColor = async (color: [number, number, number]) => { |     const middlePixelIsColor = async (color: [number, number, number]) => { | ||||||
|       return u.getGreatestPixDiff({ x: 600, y: 250 }, color) |       return u.getGreatestPixDiff({ x: 600, y: 250 }, color) | ||||||
| @ -576,9 +561,8 @@ extrude001 = extrude(50, sketch001) | |||||||
|       await page.addInitScript(async () => { |       await page.addInitScript(async () => { | ||||||
|         localStorage.setItem('persistCode', '') |         localStorage.setItem('persistCode', '') | ||||||
|       }) |       }) | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       await homePage.goToModelingScene() |       await u.waitForAuthSkipAppStart() | ||||||
|       await u.waitForPageLoad() |  | ||||||
|       await u.closeKclCodePanel() |       await u.closeKclCodePanel() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
| @ -588,7 +572,7 @@ extrude001 = extrude(50, sketch001) | |||||||
|           timeout: 5000, |           timeout: 5000, | ||||||
|           message: 'Plane color is visible', |           message: 'Plane color is visible', | ||||||
|         }) |         }) | ||||||
|         .toBeLessThanOrEqual(15) |         .toBeLessThan(15) | ||||||
|  |  | ||||||
|       let maxZoomOuts = 10 |       let maxZoomOuts = 10 | ||||||
|       let middlePixelIsBackgroundColor = |       let middlePixelIsBackgroundColor = | ||||||
| @ -606,7 +590,7 @@ extrude001 = extrude(50, sketch001) | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       expect(middlePixelIsBackgroundColor, { |       expect(middlePixelIsBackgroundColor, { | ||||||
|         message: 'We should not see the default planes', |         message: 'We no longer the default planes', | ||||||
|       }).toBeTruthy() |       }).toBeTruthy() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,20 +1,27 @@ | |||||||
| import { test, expect, Page } from './zoo-test' | import { test, expect, Page } from '@playwright/test' | ||||||
| import fs from 'node:fs/promises' | import { test as test2, expect as expect2 } from './fixtures/fixtureSetup' | ||||||
| import path from 'node:path' |  | ||||||
| import { HomePageFixture } from './fixtures/homePageFixture' |  | ||||||
|  |  | ||||||
| import { | import { | ||||||
|   getMovementUtils, |   getMovementUtils, | ||||||
|   getUtils, |   getUtils, | ||||||
|   PERSIST_MODELING_CONTEXT, |   PERSIST_MODELING_CONTEXT, | ||||||
|  |   setup, | ||||||
|  |   tearDown, | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
| import { uuidv4, roundOff } from 'lib/utils' | import { uuidv4, roundOff } from 'lib/utils' | ||||||
|  |  | ||||||
|  | test.beforeEach(async ({ context, page }, testInfo) => { | ||||||
|  |   await setup(context, page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | test.afterEach(async ({ page }, testInfo) => { | ||||||
|  |   await tearDown(page, testInfo) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test.describe('Sketch tests', () => { | test.describe('Sketch tests', () => { | ||||||
|   test('multi-sketch file shows multiple Edit Sketch buttons', async ({ |   test('multi-sketch file shows multiple Edit Sketch buttons', async ({ | ||||||
|     page, |     page, | ||||||
|     context, |     context, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     const selectionsSnippets = { |     const selectionsSnippets = { | ||||||
| @ -72,9 +79,9 @@ test.describe('Sketch tests', () => { | |||||||
|       }, |       }, | ||||||
|       selectionsSnippets |       selectionsSnippets | ||||||
|     ) |     ) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -82,23 +89,25 @@ test.describe('Sketch tests', () => { | |||||||
|     await u.closeDebugPanel() |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|     await page.getByText(selectionsSnippets.startProfileAt1).click() |     await page.getByText(selectionsSnippets.startProfileAt1).click() | ||||||
|  |     await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Edit Sketch' }) |       page.getByRole('button', { name: 'Edit Sketch' }) | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|  |  | ||||||
|     await page.getByText(selectionsSnippets.startProfileAt2).click() |     await page.getByText(selectionsSnippets.startProfileAt2).click() | ||||||
|  |     await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Edit Sketch' }) |       page.getByRole('button', { name: 'Edit Sketch' }) | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|  |  | ||||||
|     await page.getByText(selectionsSnippets.startProfileAt3).click() |     await page.getByText(selectionsSnippets.startProfileAt3).click() | ||||||
|  |     await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Edit Sketch' }) |       page.getByRole('button', { name: 'Edit Sketch' }) | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|   }) |   }) | ||||||
|   test('Can delete most of a sketch and the line tool will still work', async ({ |   test('Can delete most of a sketch and the line tool will still work', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
| @ -111,9 +120,12 @@ test.describe('Sketch tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await expect(async () => { |     await expect(async () => { | ||||||
|  |       await page.mouse.click(700, 200) | ||||||
|       await page.getByText('tangentialArcTo([24.95, -5.38], %)').click() |       await page.getByText('tangentialArcTo([24.95, -5.38], %)').click() | ||||||
|       await expect( |       await expect( | ||||||
|         page.getByRole('button', { name: 'Edit Sketch' }) |         page.getByRole('button', { name: 'Edit Sketch' }) | ||||||
| @ -130,7 +142,8 @@ test.describe('Sketch tests', () => { | |||||||
|     await page.keyboard.press('Home') |     await page.keyboard.press('Home') | ||||||
|     await page.keyboard.up('Shift') |     await page.keyboard.up('Shift') | ||||||
|     await page.keyboard.press('Backspace') |     await page.keyboard.press('Backspace') | ||||||
|     await u.openDebugPanel() |     await u.openAndClearDebugPanel() | ||||||
|  |  | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) |     await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |  | ||||||
| @ -138,31 +151,26 @@ test.describe('Sketch tests', () => { | |||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|     await expect(async () => { |     await expect(async () => { | ||||||
|       await page.mouse.move(700, 200, { steps: 25 }) |  | ||||||
|       await page.mouse.click(700, 200) |       await page.mouse.click(700, 200) | ||||||
|  |  | ||||||
|       await expect |       await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) | ||||||
|         .poll(u.crushKclCodeIntoOneLineAndThenMaybeSome, { timeout: 1000 }) |         .toBe(`sketch001 = startSketchOn('XZ') | ||||||
|         .toBe( |   |> startProfileAt([12.34, -12.34], %) | ||||||
|           `sketch001 = startSketchOn('XZ') |   |> yLine(12.34, %) | ||||||
|   |> startProfileAt([4.61,-14.01], %) |  | ||||||
|   |> yLine(15.95, %) | `) | ||||||
| ` |  | ||||||
|             .replaceAll(' ', '') |  | ||||||
|             .replaceAll('\n', '') |  | ||||||
|         ) |  | ||||||
|     }).toPass({ timeout: 40_000, intervals: [1_000] }) |     }).toPass({ timeout: 40_000, intervals: [1_000] }) | ||||||
|   }) |   }) | ||||||
|  |   test('Can exit selection of face', async ({ page }) => { | ||||||
|   test('Can exit selection of face', async ({ page, homePage }) => { |  | ||||||
|     // Load the app with the code panes |     // Load the app with the code panes | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem('persistCode', ``) |       localStorage.setItem('persistCode', ``) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     const u = await getUtils(page) | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() |     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||||
|     await expect( |     await expect( | ||||||
| @ -179,7 +187,6 @@ test.describe('Sketch tests', () => { | |||||||
|   test.describe('Can edit segments by dragging their handles', () => { |   test.describe('Can edit segments by dragging their handles', () => { | ||||||
|     const doEditSegmentsByDraggingHandle = async ( |     const doEditSegmentsByDraggingHandle = async ( | ||||||
|       page: Page, |       page: Page, | ||||||
|       homePage: HomePageFixture, |  | ||||||
|       openPanes: string[] |       openPanes: string[] | ||||||
|     ) => { |     ) => { | ||||||
|       // Load the app with the code panes |       // Load the app with the code panes | ||||||
| @ -195,8 +202,9 @@ test.describe('Sketch tests', () => { | |||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|       await homePage.goToModelingScene() |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |       await u.waitForAuthSkipAppStart() | ||||||
|       await expect( |       await expect( | ||||||
|         page.getByRole('button', { name: 'Start Sketch' }) |         page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|       ).not.toBeDisabled() |       ).not.toBeDisabled() | ||||||
| @ -310,7 +318,7 @@ test.describe('Sketch tests', () => { | |||||||
|       |> line([1.97, 2.06], %) |       |> line([1.97, 2.06], %) | ||||||
|       |> close(%)`) |       |> close(%)`) | ||||||
|     } |     } | ||||||
|     test('code pane open at start-handles', async ({ page, homePage }) => { |     test('code pane open at start-handles', async ({ page }) => { | ||||||
|       // Load the app with the code panes |       // Load the app with the code panes | ||||||
|       await page.addInitScript(async () => { |       await page.addInitScript(async () => { | ||||||
|         localStorage.setItem( |         localStorage.setItem( | ||||||
| @ -323,10 +331,10 @@ test.describe('Sketch tests', () => { | |||||||
|           }) |           }) | ||||||
|         ) |         ) | ||||||
|       }) |       }) | ||||||
|       await doEditSegmentsByDraggingHandle(page, homePage, ['code']) |       await doEditSegmentsByDraggingHandle(page, ['code']) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     test('code pane closed at start-handles', async ({ page, homePage }) => { |     test('code pane closed at start-handles', async ({ page }) => { | ||||||
|       // Load the app with the code panes |       // Load the app with the code panes | ||||||
|       await page.addInitScript(async (persistModelingContext) => { |       await page.addInitScript(async (persistModelingContext) => { | ||||||
|         localStorage.setItem( |         localStorage.setItem( | ||||||
| @ -334,14 +342,12 @@ test.describe('Sketch tests', () => { | |||||||
|           JSON.stringify({ openPanes: [] }) |           JSON.stringify({ openPanes: [] }) | ||||||
|         ) |         ) | ||||||
|       }, PERSIST_MODELING_CONTEXT) |       }, PERSIST_MODELING_CONTEXT) | ||||||
|       await doEditSegmentsByDraggingHandle(page, homePage, []) |       await doEditSegmentsByDraggingHandle(page, []) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test('Can edit a circle center and radius by dragging its handles', async ({ |   test('Can edit a circle center and radius by dragging its handles', async ({ | ||||||
|     page, |     page, | ||||||
|     editor, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
| @ -352,8 +358,9 @@ test.describe('Sketch tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|     ).not.toBeDisabled() |     ).not.toBeDisabled() | ||||||
| @ -392,7 +399,6 @@ test.describe('Sketch tests', () => { | |||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() |     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||||
|     await page.waitForTimeout(400) |     await page.waitForTimeout(400) | ||||||
|  |  | ||||||
|     let prevContent = await page.locator('.cm-content').innerText() |     let prevContent = await page.locator('.cm-content').innerText() | ||||||
|  |  | ||||||
|     await expect(page.getByTestId('segment-overlay')).toHaveCount(1) |     await expect(page.getByTestId('segment-overlay')).toHaveCount(1) | ||||||
| @ -403,9 +409,7 @@ test.describe('Sketch tests', () => { | |||||||
|         targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX }, |         targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX }, | ||||||
|       }) |       }) | ||||||
|       await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|  |       await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||||
|       await editor.expectEditor.not.toContain(prevContent) |  | ||||||
|  |  | ||||||
|       prevContent = await page.locator('.cm-content').innerText() |       prevContent = await page.locator('.cm-content').innerText() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
| @ -418,20 +422,18 @@ test.describe('Sketch tests', () => { | |||||||
|         sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, |         sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, | ||||||
|         targetPosition: { x: lineEnd.x + dragPX * 2, y: lineEnd.y + dragPX }, |         targetPosition: { x: lineEnd.x + dragPX * 2, y: lineEnd.y + dragPX }, | ||||||
|       }) |       }) | ||||||
|       await editor.expectEditor.not.toContain(prevContent) |       await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||||
|       prevContent = await page.locator('.cm-content').innerText() |       prevContent = await page.locator('.cm-content').innerText() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     // expect the code to have changed |     // expect the code to have changed | ||||||
|     await editor.expectEditor.toContain( |     await expect(page.locator('.cm-content')) | ||||||
|       `sketch001 = startSketchOn('XZ') |       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> circle({ center = [7.26, -2.37], radius = 11.44 }, %)`, |   |> circle({ center = [7.26, -2.37], radius = 11.44 }, %) | ||||||
|       { shouldNormalise: true } | `) | ||||||
|     ) |  | ||||||
|   }) |   }) | ||||||
|   test('Can edit a sketch that has been extruded in the same pipe', async ({ |   test('Can edit a sketch that has been extruded in the same pipe', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
| @ -446,8 +448,9 @@ test.describe('Sketch tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|     ).not.toBeDisabled() |     ).not.toBeDisabled() | ||||||
| @ -501,11 +504,11 @@ test.describe('Sketch tests', () => { | |||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|     const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') |     const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') | ||||||
|     await page.dragAndDrop('#stream', '#stream', { |  | ||||||
|       sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y }, |  | ||||||
|       targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 }, |  | ||||||
|     }) |  | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |     await page.dragAndDrop('#stream', '#stream', { | ||||||
|  |       sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, | ||||||
|  |       targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX }, | ||||||
|  |     }) | ||||||
|     await expect(page.locator('.cm-content')).not.toHaveText(prevContent) |     await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||||
|     prevContent = await page.locator('.cm-content').innerText() |     prevContent = await page.locator('.cm-content').innerText() | ||||||
|  |  | ||||||
| @ -514,8 +517,8 @@ test.describe('Sketch tests', () => { | |||||||
|     await page.dragAndDrop('#stream', '#stream', { |     await page.dragAndDrop('#stream', '#stream', { | ||||||
|       sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 }, |       sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 }, | ||||||
|       targetPosition: { |       targetPosition: { | ||||||
|         x: tangentEnd.x, |         x: tangentEnd.x + dragPX, | ||||||
|         y: tangentEnd.y - 15, |         y: tangentEnd.y + dragPX, | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
| @ -525,8 +528,8 @@ test.describe('Sketch tests', () => { | |||||||
|     await expect(page.locator('.cm-content')) |     await expect(page.locator('.cm-content')) | ||||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') |       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt([7.12, -12.68], %) |   |> startProfileAt([7.12, -12.68], %) | ||||||
|     |> line([12.68, -1.09], %) |   |> line([15.39, -2.78], %) | ||||||
|     |> tangentialArcTo([24.89, 0.68], %) |   |> tangentialArcTo([27.6, -3.05], %) | ||||||
|   |> close(%) |   |> close(%) | ||||||
|   |> extrude(5, %) |   |> extrude(5, %) | ||||||
| `) | `) | ||||||
| @ -534,7 +537,6 @@ test.describe('Sketch tests', () => { | |||||||
|  |  | ||||||
|   test('Can edit a sketch that has been revolved in the same pipe', async ({ |   test('Can edit a sketch that has been revolved in the same pipe', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
| @ -549,8 +551,9 @@ test.describe('Sketch tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
|     ).not.toBeDisabled() |     ).not.toBeDisabled() | ||||||
| @ -633,15 +636,12 @@ test.describe('Sketch tests', () => { | |||||||
|     |> close(%) |     |> close(%) | ||||||
|     |> revolve({ axis = "X" }, %)`) |     |> revolve({ axis = "X" }, %)`) | ||||||
|   }) |   }) | ||||||
|   test('Can add multiple sketches', async ({ page, homePage }) => { |   test('Can add multiple sketches', async ({ page }) => { | ||||||
|     // TODO: fix this test on windows after the electron migration |  | ||||||
|     test.skip(process.platform === 'win32', 'Skip on windows') |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|     const viewportSize = { width: 1200, height: 500 } |     const viewportSize = { width: 1200, height: 500 } | ||||||
|     await page.setBodyDimensions(viewportSize) |     await page.setViewportSize(viewportSize) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|  |  | ||||||
|     const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 } |     const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 } | ||||||
| @ -736,8 +736,9 @@ test.describe('Sketch tests', () => { | |||||||
|       scale = 1 |       scale = 1 | ||||||
|     ) => { |     ) => { | ||||||
|       const u = await getUtils(page) |       const u = await getUtils(page) | ||||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |       await u.waitForAuthSkipAppStart() | ||||||
|       await u.openDebugPanel() |       await u.openDebugPanel() | ||||||
|  |  | ||||||
|       const code = `sketch001 = startSketchOn('-XZ') |       const code = `sketch001 = startSketchOn('-XZ') | ||||||
| @ -819,22 +820,17 @@ test.describe('Sketch tests', () => { | |||||||
|       await u.expectCmdLog('[data-message-type="execution-done"]') |       await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|       await u.removeCurrentCode() |       await u.removeCurrentCode() | ||||||
|     } |     } | ||||||
|     test('[0, 100, 100]', async ({ page, homePage }) => { |     test('[0, 100, 100]', async ({ page }) => { | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|       await doSnapAtDifferentScales(page, [0, 100, 100], 0.01) |       await doSnapAtDifferentScales(page, [0, 100, 100], 0.01) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     test('[0, 10000, 10000]', async ({ page, homePage }) => { |     test('[0, 10000, 10000]', async ({ page }) => { | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|       await doSnapAtDifferentScales(page, [0, 10000, 10000]) |       await doSnapAtDifferentScales(page, [0, 10000, 10000]) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|   test('exiting a close extrude, has the extrude button enabled ready to go', async ({ |   test('exiting a close extrude, has the extrude button enabled ready to go', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     // TODO: fix this test on windows after the electron migration |  | ||||||
|     test.skip(process.platform === 'win32', 'Skip on windows') |  | ||||||
|     // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 |     // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -851,9 +847,9 @@ test.describe('Sketch tests', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // wait for execution done |     // wait for execution done | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
| @ -889,10 +885,7 @@ test.describe('Sketch tests', () => { | |||||||
|       timeout: 10_000, |       timeout: 10_000, | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|   test("Existing sketch with bad code delete user's code", async ({ |   test("Existing sketch with bad code delete user's code", async ({ page }) => { | ||||||
|     page, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|     // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 |     // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -910,9 +903,9 @@ test.describe('Sketch tests', () => { | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -950,10 +943,118 @@ test.describe('Sketch tests', () => { | |||||||
| `.replace(/\s/g, '') | `.replace(/\s/g, '') | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
|   // TODO: fix after electron migration is merged |  | ||||||
|   test.fixme( |   /* TODO: once we fix bug turn on. | ||||||
|     'empty-scene default-planes act as expected', |    test('empty-scene default-planes act as expected when spaces in file', async ({ | ||||||
|     async ({ page, homePage }) => { |     page, | ||||||
|  |     browserName, | ||||||
|  |   }) => { | ||||||
|  |  | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     const XYPlanePoint = { x: 774, y: 116 } as const | ||||||
|  |     const unHoveredColor: [number, number, number] = [47, 47, 93] | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await u.openAndClearDebugPanel() | ||||||
|  |  | ||||||
|  |     // Fill with spaces | ||||||
|  |     await u.codeLocator.fill(`                | ||||||
|  | `) | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   test('empty-scene default-planes act as expected when only code comments in file', async ({ | ||||||
|  |     page, | ||||||
|  |     browserName, | ||||||
|  |   }) => { | ||||||
|  |  | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     const XYPlanePoint = { x: 774, y: 116 } as const | ||||||
|  |     const unHoveredColor: [number, number, number] = [47, 47, 93] | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await u.openAndClearDebugPanel() | ||||||
|  |  | ||||||
|  |     // Fill with spaces | ||||||
|  |     await u.codeLocator.fill(`// this is a code comments ya nerds | ||||||
|  | `) | ||||||
|  |  | ||||||
|  |     await u.openDebugPanel() | ||||||
|  |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |     await u.closeDebugPanel() | ||||||
|  |  | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |  | ||||||
|  |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) | ||||||
|  |     await page.waitForTimeout(200) | ||||||
|  |  | ||||||
|  |     // color should not change for having been hovered | ||||||
|  |     expect( | ||||||
|  |       await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor) | ||||||
|  |     ).toBeLessThan(8) | ||||||
|  |   })*/ | ||||||
|  |  | ||||||
|  |   test('empty-scene default-planes act as expected', async ({ | ||||||
|  |     page, | ||||||
|  |     browserName, | ||||||
|  |   }) => { | ||||||
|  |     test.skip( | ||||||
|  |       browserName === 'webkit', | ||||||
|  |       'Skip on Safari until `window.tearDown` is working there' | ||||||
|  |     ) | ||||||
|     /** |     /** | ||||||
|      * Tests the following things |      * Tests the following things | ||||||
|      * 1) The the planes are there on load because the scene is empty |      * 1) The the planes are there on load because the scene is empty | ||||||
| @ -966,7 +1067,9 @@ test.describe('Sketch tests', () => { | |||||||
|      */ |      */ | ||||||
|  |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|       await homePage.goToModelingScene() |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -1014,7 +1117,7 @@ test.describe('Sketch tests', () => { | |||||||
|  |  | ||||||
|     // click start Sketch |     // click start Sketch | ||||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() |     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||||
|       await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 }) |     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 5 }) | ||||||
|     const hoveredColor: [number, number, number] = [93, 93, 127] |     const hoveredColor: [number, number, number] = [93, 93, 127] | ||||||
|     // now that we're expecting the user to select a plan, it does respond to hover |     // now that we're expecting the user to select a plan, it does respond to hover | ||||||
|     await expect |     await expect | ||||||
| @ -1041,6 +1144,8 @@ test.describe('Sketch tests', () => { | |||||||
| ` | ` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |     await page.reload() | ||||||
|  |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -1050,12 +1155,18 @@ test.describe('Sketch tests', () => { | |||||||
|     expect( |     expect( | ||||||
|       await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor) |       await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor) | ||||||
|     ).toBeLessThan(3) |     ).toBeLessThan(3) | ||||||
|     } |   }) | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   test('Can attempt to sketch on revolved face', async ({ page, homePage }) => { |   test('Can attempt to sketch on revolved face', async ({ | ||||||
|  |     page, | ||||||
|  |     browserName, | ||||||
|  |   }) => { | ||||||
|  |     test.skip( | ||||||
|  |       browserName === 'webkit', | ||||||
|  |       'Skip on Safari until `window.tearDown` is working there' | ||||||
|  |     ) | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     await page.addInitScript(async () => { |     await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |       localStorage.setItem( | ||||||
| @ -1080,7 +1191,7 @@ test.describe('Sketch tests', () => { | |||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
| @ -1112,10 +1223,9 @@ test.describe('Sketch tests', () => { | |||||||
|  |  | ||||||
|   test('Can sketch on face when user defined function was used in the sketch', async ({ |   test('Can sketch on face when user defined function was used in the sketch', async ({ | ||||||
|     page, |     page, | ||||||
|     homePage, |  | ||||||
|   }) => { |   }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     // Checking for a regression that performs a sketch when a user defined function |     // Checking for a regression that performs a sketch when a user defined function | ||||||
|     // is declared at the top of the file and used in the sketch that is being drawn on. |     // is declared at the top of the file and used in the sketch that is being drawn on. | ||||||
| @ -1169,7 +1279,7 @@ test.describe('Sketch tests', () => { | |||||||
|  |  | ||||||
|     const center = { x: 600, y: 250 } |     const center = { x: 600, y: 250 } | ||||||
|     const rectangleSize = 20 |     const rectangleSize = 20 | ||||||
|     await homePage.goToModelingScene() |     await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|     // Start a sketch |     // Start a sketch | ||||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() |     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||||
| @ -1208,33 +1318,27 @@ test.describe('Sketch tests', () => { | |||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test.describe('Sketch mode should be toleratant to syntax errors', () => { | test2.describe('Sketch mode should be toleratant to syntax errors', () => { | ||||||
|   test( |   test2( | ||||||
|     'adding a syntax error, recovers after fixing', |     'adding a syntax error, recovers after fixing', | ||||||
|     { tag: ['@skipWin'] }, |     { tag: ['@skipWin'] }, | ||||||
|     async ({ page, homePage, context, scene, editor, toolbar }) => { |     async ({ app, scene, editor, toolbar }) => { | ||||||
|       const file = await fs.readFile( |       test.skip( | ||||||
|         path.resolve( |         process.platform === 'win32', | ||||||
|           __dirname, |         'a codemirror error appears in this test only on windows, that causes the test to fail only because of our "no new error" logic, but it can not be replicated locally' | ||||||
|           '../../', |  | ||||||
|           './src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer.kcl' |  | ||||||
|         ), |  | ||||||
|         'utf-8' |  | ||||||
|       ) |       ) | ||||||
|       await context.addInitScript((file) => { |       const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl') | ||||||
|         localStorage.setItem('persistCode', file) |       await app.initialise(file) | ||||||
|       }, file) |  | ||||||
|       await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|       const [objClick] = scene.makeMouseHelpers(600, 250) |       const [objClick] = scene.makeMouseHelpers(600, 250) | ||||||
|       const arrowHeadLocation = { x: 706, y: 129 } as const |       const arrowHeadLocation = { x: 604, y: 129 } as const | ||||||
|       const arrowHeadWhite: [number, number, number] = [255, 255, 255] |       const arrowHeadWhite: [number, number, number] = [255, 255, 255] | ||||||
|       const backgroundGray: [number, number, number] = [28, 28, 28] |       const backgroundGray: [number, number, number] = [28, 28, 28] | ||||||
|       const verifyArrowHeadColor = async (c: [number, number, number]) => |       const verifyArrowHeadColor = async (c: [number, number, number]) => | ||||||
|         scene.expectPixelColor(c, arrowHeadLocation, 15) |         scene.expectPixelColor(c, arrowHeadLocation, 15) | ||||||
|  |  | ||||||
|       await test.step('check chamfer selection changes cursor positon', async () => { |       await test.step('check chamfer selection changes cursor positon', async () => { | ||||||
|         await expect(async () => { |         await expect2(async () => { | ||||||
|           // sometimes initial click doesn't register |           // sometimes initial click doesn't register | ||||||
|           await objClick() |           await objClick() | ||||||
|           await editor.expectActiveLinesToBe([ |           await editor.expectActiveLinesToBe([ | ||||||
| @ -1270,36 +1374,24 @@ test.describe('Sketch mode should be toleratant to syntax errors', () => { | |||||||
|         // this checks sketch segments have been drawn |         // this checks sketch segments have been drawn | ||||||
|         await verifyArrowHeadColor(arrowHeadWhite) |         await verifyArrowHeadColor(arrowHeadWhite) | ||||||
|       }) |       }) | ||||||
|       await page.waitForTimeout(100) |       await app.page.waitForTimeout(100) | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test.describe(`Sketching with offset planes`, () => { | test2.describe(`Sketching with offset planes`, () => { | ||||||
|   test(`Can select an offset plane to sketch on`, async ({ |   test2( | ||||||
|     context, |     `Can select an offset plane to sketch on`, | ||||||
|     page, |     async ({ app, scene, toolbar, editor }) => { | ||||||
|     scene, |  | ||||||
|     toolbar, |  | ||||||
|     editor, |  | ||||||
|     homePage, |  | ||||||
|   }) => { |  | ||||||
|       // We seed the scene with a single offset plane |       // We seed the scene with a single offset plane | ||||||
|     await context.addInitScript(() => { |       await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`) | ||||||
|       localStorage.setItem( |  | ||||||
|         'persistCode', |  | ||||||
|         `offsetPlane001 = offsetPlane("XY", 10)` |  | ||||||
|       ) |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await homePage.goToModelingScene() |  | ||||||
|  |  | ||||||
|       const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200) |       const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200) | ||||||
|  |  | ||||||
|     await test.step(`Start sketching on the offset plane`, async () => { |       await test2.step(`Start sketching on the offset plane`, async () => { | ||||||
|         await toolbar.startSketchPlaneSelection() |         await toolbar.startSketchPlaneSelection() | ||||||
|  |  | ||||||
|       await test.step(`Hovering should highlight code`, async () => { |         await test2.step(`Hovering should highlight code`, async () => { | ||||||
|           await planeHover() |           await planeHover() | ||||||
|           await editor.expectState({ |           await editor.expectState({ | ||||||
|             activeLines: [`offsetPlane001=offsetPlane("XY",10)`], |             activeLines: [`offsetPlane001=offsetPlane("XY",10)`], | ||||||
| @ -1308,18 +1400,22 @@ test.describe(`Sketching with offset planes`, () => { | |||||||
|           }) |           }) | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|       await test.step(`Clicking should select the plane and enter sketch mode`, async () => { |         await test2.step( | ||||||
|  |           `Clicking should select the plane and enter sketch mode`, | ||||||
|  |           async () => { | ||||||
|             await planeClick() |             await planeClick() | ||||||
|             // Have to wait for engine-side animation to finish |             // Have to wait for engine-side animation to finish | ||||||
|         await page.waitForTimeout(600) |             await app.page.waitForTimeout(600) | ||||||
|         await expect(toolbar.lineBtn).toBeEnabled() |             await expect2(toolbar.lineBtn).toBeEnabled() | ||||||
|             await editor.expectEditor.toContain('startSketchOn(offsetPlane001)') |             await editor.expectEditor.toContain('startSketchOn(offsetPlane001)') | ||||||
|             await editor.expectState({ |             await editor.expectState({ | ||||||
|               activeLines: [`offsetPlane001=offsetPlane("XY",10)`], |               activeLines: [`offsetPlane001=offsetPlane("XY",10)`], | ||||||
|               diagnostics: [], |               diagnostics: [], | ||||||
|               highlightedCode: '', |               highlightedCode: '', | ||||||
|             }) |             }) | ||||||
|  |           } | ||||||
|  |         ) | ||||||
|       }) |       }) | ||||||
|     }) |     } | ||||||
|   }) |   ) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -47,11 +47,7 @@ test.beforeEach(async ({ page }) => { | |||||||
|  |  | ||||||
| test.setTimeout(60_000) | test.setTimeout(60_000) | ||||||
|  |  | ||||||
| // We test this end to end already - getting this to work on web just to take | test( | ||||||
| // a snapshot of it feels weird. I'd rather our regular tests fail. |  | ||||||
| // The primary failure is doExport now relies on the filesystem. We can follow |  | ||||||
| // up with another PR if we want this back. |  | ||||||
| test.skip( |  | ||||||
|   'exports of each format should work', |   'exports of each format should work', | ||||||
|   { tag: ['@snapshot', '@skipWin', '@skipMacos'] }, |   { tag: ['@snapshot', '@skipWin', '@skipMacos'] }, | ||||||
|   async ({ page, context }) => { |   async ({ page, context }) => { | ||||||
| @ -375,7 +371,6 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => { | |||||||
|   await u.closeKclCodePanel() |   await u.closeKclCodePanel() | ||||||
|   await expect(page).toHaveScreenshot({ |   await expect(page).toHaveScreenshot({ | ||||||
|     maxDiffPixels: 100, |     maxDiffPixels: 100, | ||||||
|     mask: [page.getByTestId('model-state-indicator')], |  | ||||||
|   }) |   }) | ||||||
|   await u.openKclCodePanel() |   await u.openKclCodePanel() | ||||||
| } | } | ||||||
| @ -955,75 +950,7 @@ test( | |||||||
|  |  | ||||||
| test.describe('Grid visibility', { tag: '@snapshot' }, () => { | test.describe('Grid visibility', { tag: '@snapshot' }, () => { | ||||||
|   // FIXME: Skip on macos its being weird. |   // FIXME: Skip on macos its being weird. | ||||||
|   // test.skip(process.platform === 'darwin', 'Skip on macos') |   test.skip(process.platform === 'darwin', 'Skip on macos') | ||||||
|  |  | ||||||
|   test('Grid turned off to on via command bar', async ({ page }) => { |  | ||||||
|     const u = await getUtils(page) |  | ||||||
|     const stream = page.getByTestId('stream') |  | ||||||
|     const mask = [ |  | ||||||
|       page.locator('#app-header'), |  | ||||||
|       page.locator('#sidebar-top-ribbon'), |  | ||||||
|       page.locator('#sidebar-bottom-ribbon'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     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(1) |  | ||||||
|     await u.closeDebugPanel() |  | ||||||
|     await u.closeKclCodePanel() |  | ||||||
|     // 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) |  | ||||||
|  |  | ||||||
|     // Open the command bar. |  | ||||||
|     await page |  | ||||||
|       .getByRole('button', { name: 'Commands', exact: false }) |  | ||||||
|       .or(page.getByRole('button', { name: '⌘K' })) |  | ||||||
|       .click() |  | ||||||
|     const commandName = 'show scale grid' |  | ||||||
|     const commandOption = page.getByRole('option', { |  | ||||||
|       name: commandName, |  | ||||||
|       exact: false, |  | ||||||
|     }) |  | ||||||
|     const cmdSearchBar = page.getByPlaceholder('Search commands') |  | ||||||
|     // This selector changes after we set the setting |  | ||||||
|     await cmdSearchBar.fill(commandName) |  | ||||||
|     await expect(commandOption).toBeVisible() |  | ||||||
|     await commandOption.click() |  | ||||||
|  |  | ||||||
|     const toggleInput = page.getByPlaceholder('Off') |  | ||||||
|     await expect(toggleInput).toBeVisible() |  | ||||||
|     await expect(toggleInput).toBeFocused() |  | ||||||
|  |  | ||||||
|     // Select On |  | ||||||
|     await page.keyboard.press('ArrowDown') |  | ||||||
|     await expect(page.getByRole('option', { name: 'Off' })).toHaveAttribute( |  | ||||||
|       'data-headlessui-state', |  | ||||||
|       'active selected' |  | ||||||
|     ) |  | ||||||
|     await page.keyboard.press('ArrowUp') |  | ||||||
|     await expect(page.getByRole('option', { name: 'On' })).toHaveAttribute( |  | ||||||
|       'data-headlessui-state', |  | ||||||
|       'active' |  | ||||||
|     ) |  | ||||||
|     await page.keyboard.press('Enter') |  | ||||||
|  |  | ||||||
|     // Check the toast appeared |  | ||||||
|     await expect( |  | ||||||
|       page.getByText(`Set show scale grid to "true" as a user default`) |  | ||||||
|     ).toBeVisible() |  | ||||||
|  |  | ||||||
|     await expect(stream).toHaveScreenshot({ |  | ||||||
|       maxDiffPixels: 100, |  | ||||||
|       mask, |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   test('Grid turned off', async ({ page }) => { |   test('Grid turned off', async ({ page }) => { | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
| @ -1169,109 +1096,3 @@ test.fixme('theme persists', async ({ page, context }) => { | |||||||
|     maxDiffPixels: 100, |     maxDiffPixels: 100, | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test.describe('code color goober', { tag: '@snapshot' }, () => { |  | ||||||
|   test('code color goober', async ({ page, context }) => { |  | ||||||
|     const u = await getUtils(page) |  | ||||||
|     await context.addInitScript(async () => { |  | ||||||
|       localStorage.setItem( |  | ||||||
|         'persistCode', |  | ||||||
|         `// Create a pipe using a sweep. |  | ||||||
|  |  | ||||||
| // Create a path for the sweep. |  | ||||||
| sweepPath = startSketchOn('XZ') |  | ||||||
|   |> startProfileAt([0.05, 0.05], %) |  | ||||||
|   |> line([0, 7], %) |  | ||||||
|   |> tangentialArc({ offset = 90, radius = 5 }, %) |  | ||||||
|   |> line([-3, 0], %) |  | ||||||
|   |> tangentialArc({ offset = -90, radius = 5 }, %) |  | ||||||
|   |> line([0, 7], %) |  | ||||||
|  |  | ||||||
| sweepSketch = startSketchOn('XY') |  | ||||||
|   |> startProfileAt([2, 0], %) |  | ||||||
|   |> arc({ |  | ||||||
|        angleEnd = 360, |  | ||||||
|        angleStart = 0, |  | ||||||
|        radius = 2 |  | ||||||
|      }, %) |  | ||||||
|   |> sweep({ |  | ||||||
|     path = sweepPath, |  | ||||||
|   }, %) |  | ||||||
|   |> appearance({ |  | ||||||
|        color = "#bb00ff", |  | ||||||
|        metalness = 90, |  | ||||||
|        roughness = 90 |  | ||||||
|      }, %) |  | ||||||
| ` |  | ||||||
|       ) |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await page.setViewportSize({ width: 1200, height: 1000 }) |  | ||||||
|  |  | ||||||
|     await u.waitForAuthSkipAppStart() |  | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |  | ||||||
|     await u.clearAndCloseDebugPanel() |  | ||||||
|  |  | ||||||
|     await expect(page, 'expect small color widget').toHaveScreenshot({ |  | ||||||
|       maxDiffPixels: 100, |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   test('code color goober opening window', async ({ page, context }) => { |  | ||||||
|     const u = await getUtils(page) |  | ||||||
|     await context.addInitScript(async () => { |  | ||||||
|       localStorage.setItem( |  | ||||||
|         'persistCode', |  | ||||||
|         `// Create a pipe using a sweep. |  | ||||||
|  |  | ||||||
| // Create a path for the sweep. |  | ||||||
| sweepPath = startSketchOn('XZ') |  | ||||||
|   |> startProfileAt([0.05, 0.05], %) |  | ||||||
|   |> line([0, 7], %) |  | ||||||
|   |> tangentialArc({ offset = 90, radius = 5 }, %) |  | ||||||
|   |> line([-3, 0], %) |  | ||||||
|   |> tangentialArc({ offset = -90, radius = 5 }, %) |  | ||||||
|   |> line([0, 7], %) |  | ||||||
|  |  | ||||||
| sweepSketch = startSketchOn('XY') |  | ||||||
|   |> startProfileAt([2, 0], %) |  | ||||||
|   |> arc({ |  | ||||||
|        angleEnd = 360, |  | ||||||
|        angleStart = 0, |  | ||||||
|        radius = 2 |  | ||||||
|      }, %) |  | ||||||
|   |> sweep({ |  | ||||||
|     path = sweepPath, |  | ||||||
|   }, %) |  | ||||||
|   |> appearance({ |  | ||||||
|        color = "#bb00ff", |  | ||||||
|        metalness = 90, |  | ||||||
|        roughness = 90 |  | ||||||
|      }, %) |  | ||||||
| ` |  | ||||||
|       ) |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await page.setViewportSize({ width: 1200, height: 1000 }) |  | ||||||
|  |  | ||||||
|     await u.waitForAuthSkipAppStart() |  | ||||||
|  |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |  | ||||||
|     await u.clearAndCloseDebugPanel() |  | ||||||
|  |  | ||||||
|     await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible() |  | ||||||
|  |  | ||||||
|     // Click the color widget |  | ||||||
|     await page.locator('.cm-css-color-picker-wrapper input').click() |  | ||||||
|  |  | ||||||
|     await expect( |  | ||||||
|       page, |  | ||||||
|       'expect small color widget to have window open' |  | ||||||
|     ).toHaveScreenshot({ |  | ||||||
|       maxDiffPixels: 100, |  | ||||||
|     }) |  | ||||||
|   }) |  | ||||||
| }) |  | ||||||
|  | |||||||
| Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB | 
| Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB | 
| Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 43 KiB | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 57 KiB | 
| Before Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 55 KiB | 
| Before Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB | 
| Before Width: | Height: | Size: 152 KiB | 
| Before Width: | Height: | Size: 144 KiB | 
| Before Width: | Height: | Size: 130 KiB | 
| Before Width: | Height: | Size: 136 KiB | 
| Before Width: | Height: | Size: 128 KiB | 
| Before Width: | Height: | Size: 112 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 42 KiB | 
