Compare commits
	
		
			56 Commits
		
	
	
		
			nightly-v2
			...
			pierremtb/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cf4655ff6a | |||
| e338c39eab | |||
| d1d98897dc | |||
| 98b4aaea84 | |||
| 1faad75462 | |||
| 496a6c2768 | |||
| 5bb1bf520e | |||
| a5377f50ff | |||
| 2fffa7e6d8 | |||
| 98c50eacc5 | |||
| 8f5c8447ea | |||
| ded9f9b89f | |||
| 086bf9b736 | |||
| 129e07f059 | |||
| 137e528944 | |||
| b9a07c9393 | |||
| f18f975e1c | |||
| 6cd1af23e6 | |||
| cc247ace0e | |||
| 68bcee9298 | |||
| c2500c39e6 | |||
| 38016e0137 | |||
| b263ef049a | |||
| 1bde0e7333 | |||
| 2301cc153a | |||
| 545332ee27 | |||
| 725e207bd1 | |||
| 75819f880e | |||
| 3c1c1303e1 | |||
| 60e62d435e | |||
| 78616f4074 | |||
| 566183ffa7 | |||
| 3e435bebf4 | |||
| faae28b025 | |||
| 2d91549a02 | |||
| 46874f4d0a | |||
| a132b35f99 | |||
| 6e04760e1c | |||
| 082b1cad74 | |||
| 84ddae384e | |||
| 7566954b13 | |||
| 4b8c37c2c8 | |||
| 6d69b560be | |||
| aa81d616cc | |||
| 7ee5a1c26b | |||
| 0763d1d11d | |||
| 0f52d7b21f | |||
| 25d252787c | |||
| 1bd5dc20ef | |||
| cd6b9c2166 | |||
| f79a59d415 | |||
| eda97a3058 | |||
| 5cbb9d0697 | |||
| be3f9b7b64 | |||
| 6179c66ae9 | |||
| 117957d7c2 | 
| @ -1,59 +0,0 @@ | ||||
| # 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,15 +1,17 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| # 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 [[ "$1" == ubuntu-latest* ]]; then | ||||
|             xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true | ||||
|         elif [[ "$1" == windows-latest* ]]; then | ||||
|             yarn test:playwright:electron:windows || true | ||||
|         elif [[ "$1" == macos-14* ]]; then | ||||
|             yarn test:playwright:electron:macos || true | ||||
|         if [[ "$3" == ubuntu-latest* ]]; then | ||||
|             xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true | ||||
|         elif [[ "$3" == windows-latest* ]]; then | ||||
|             yarn test:playwright:electron:windows -- --shard=$1/$2 || true | ||||
|         elif [[ "$3" == macos-14* ]]; then | ||||
|             yarn test:playwright:electron:macos  -- --shard=$1/$2 || true | ||||
|         else | ||||
|             echo "Do not run playwright. Unable to detect os runtime." | ||||
|             exit 1 | ||||
| @ -28,11 +30,11 @@ while [[ $retry -le $max_retrys ]]; do | ||||
|         if [[ $failed_tests -gt 0 ]]; then | ||||
|             echo "retried=true" >>$GITHUB_OUTPUT | ||||
|             echo "run playwright with last failed tests and retry $retry" | ||||
|             if [[ "$1" == ubuntu-latest* ]]; then | ||||
|             if [[ "$3" == ubuntu-latest* ]]; then | ||||
|                 xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true | ||||
|             elif [[ "$1" == windows-latest* ]]; then | ||||
|             elif [[ "$3" == windows-latest* ]]; then | ||||
|                 yarn test:playwright:electron:windows -- --last-failed || true | ||||
|             elif [[ "$1" == macos-14* ]]; then | ||||
|             elif [[ "$3" == macos-14* ]]; then | ||||
|                 yarn test:playwright:electron:macos -- --last-failed || true | ||||
|             else | ||||
|                 echo "Do not run playwright. Unable to detect os runtime." | ||||
|  | ||||
							
								
								
									
										156
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,7 +1,7 @@ | ||||
| name: E2E Tests | ||||
| on: | ||||
|   push: | ||||
|     branches: [ main ] | ||||
|     branches: [ main, pierremtb/move-tests-to-electron ] | ||||
|   pull_request: | ||||
|     branches: [ main ] | ||||
|  | ||||
| @ -33,13 +33,13 @@ jobs: | ||||
|             rust: | ||||
|               - 'src/wasm-lib/**' | ||||
|  | ||||
|   browser: | ||||
|   electron: | ||||
|     timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }} | ||||
|     name: playwright:browser:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} | ||||
|     name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: [ubuntu-latest-8-cores, windows-latest-8-cores] | ||||
|         os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large] | ||||
|         shardIndex: [1, 2, 3, 4] | ||||
|         shardTotal: [4] | ||||
|     runs-on: ${{ matrix.os }} | ||||
| @ -123,13 +123,13 @@ jobs: | ||||
|       if: steps.download-wasm.outcome == 'failure' | ||||
|       shell: bash | ||||
|       run: yarn build:wasm | ||||
|     - name: build web | ||||
|       run: yarn build:local | ||||
|     - name: build electron | ||||
|       shell: bash | ||||
|       run: yarn tron:package | ||||
|     - name: Run ubuntu/chrome snapshots | ||||
|       shell: bash | ||||
|       run: | | ||||
|         yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | ||||
|         PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | ||||
|       env: | ||||
|         CI: true | ||||
|         NODE_ENV: development | ||||
| @ -186,12 +186,12 @@ jobs: | ||||
|       with: | ||||
|         name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
|         path: test-results/ | ||||
|     - name: Run playwright/chrome flow (with retries) | ||||
|     - name: Run playwright/electron flow (with retries) | ||||
|       id: retry | ||||
|       if: ${{ !cancelled() && (success() || failure()) }} | ||||
|       shell: bash | ||||
|       run: | | ||||
|         .github/ci-cd-scripts/playwright-browser-chrome.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}} | ||||
|         .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}} | ||||
|       env: | ||||
|         CI: true | ||||
|         FAIL_ON_CONSOLE_ERRORS: true | ||||
| @ -199,11 +199,6 @@ jobs: | ||||
|         VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||
|         VITE_KC_SKIP_AUTH: true | ||||
|         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 | ||||
|       if: always() | ||||
|       with: | ||||
| @ -221,136 +216,3 @@ jobs: | ||||
|         retention-days: 30 | ||||
|         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,23 +388,6 @@ yarn test:unit:local | ||||
|  | ||||
| #### 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** | ||||
|  | ||||
| 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. | ||||
|  | ||||
| @ -1,22 +1,11 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
|  | ||||
| import { setupElectron, tearDown } from './test-utils' | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| test.describe('Electron app header tests', () => { | ||||
|   test( | ||||
|     'Open Command Palette button has correct shortcut', | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async () => {}, | ||||
|       }) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     async ({ page }, testInfo) => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       // No space before the shortcut since it checks textContent. | ||||
|       let text | ||||
| @ -34,21 +23,14 @@ test.describe('Electron app header tests', () => { | ||||
|       const commandsButton = page.getByRole('button', { name: 'Commands' }) | ||||
|       await expect(commandsButton).toBeVisible() | ||||
|       await expect(commandsButton).toHaveText(text) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     'User settings has correct shortcut', | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async () => {}, | ||||
|       }) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     async ({ page }, testInfo) => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       // Open the user sidebar menu. | ||||
|       await page.getByTestId('user-sidebar-toggle').click() | ||||
| @ -59,8 +41,6 @@ test.describe('Electron app header tests', () => { | ||||
|       const userSettingsButton = page.getByTestId('user-settings') | ||||
|       await expect(userSettingsButton).toBeVisible() | ||||
|       await expect(userSettingsButton).toHaveText(text) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| @ -1,29 +1,26 @@ | ||||
| import { test, expect, Page } from '@playwright/test' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
| import { | ||||
|   getUtils, | ||||
|   TEST_COLORS, | ||||
|   setup, | ||||
|   tearDown, | ||||
|   commonPoints, | ||||
|   PERSIST_MODELING_CONTEXT, | ||||
| } from './test-utils' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
| import { HomePageFixture } from './fixtures/homePageFixture' | ||||
|  | ||||
| test.setTimeout(120000) | ||||
|  | ||||
| async function doBasicSketch(page: Page, openPanes: string[]) { | ||||
| async function doBasicSketch( | ||||
|   page: Page, | ||||
|   homePage: HomePageFixture, | ||||
|   openPanes: string[] | ||||
| ) { | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|  | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await homePage.goToModelingScene() | ||||
|   await u.waitForPageLoad() | ||||
|   await page.waitForTimeout() | ||||
|   await u.openDebugPanel() | ||||
|  | ||||
|   // If we have the code pane open, we should see the code. | ||||
| @ -148,13 +145,11 @@ async function doBasicSketch(page: Page, openPanes: string[]) { | ||||
| } | ||||
|  | ||||
| test.describe('Basic sketch', () => { | ||||
|   test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => { | ||||
|     // Skip on windows it is being weird. | ||||
|     test.skip(process.platform === 'win32', 'Skip on windows') | ||||
|     await doBasicSketch(page, ['code']) | ||||
|   test.fixme('code pane open at start', async ({ page, homePage }) => { | ||||
|     await doBasicSketch(page, homePage, ['code']) | ||||
|   }) | ||||
|  | ||||
|   test('code pane closed at start', async ({ page }) => { | ||||
|   test.fixme('code pane closed at start', async ({ page, homePage }) => { | ||||
|     // Load the app with the code panes | ||||
|     await page.addInitScript(async (persistModelingContext) => { | ||||
|       localStorage.setItem( | ||||
| @ -162,6 +157,6 @@ test.describe('Basic sketch', () => { | ||||
|         JSON.stringify({ openPanes: [] }) | ||||
|       ) | ||||
|     }, PERSIST_MODELING_CONTEXT) | ||||
|     await doBasicSketch(page, []) | ||||
|     await doBasicSketch(page, homePage, []) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -1,27 +1,21 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
| import { HomePageFixture } from './fixtures/homePageFixture' | ||||
| import { getUtils } from './test-utils' | ||||
| import { EngineCommand } from 'lang/std/artifactGraph' | ||||
| 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', () => { | ||||
|   const sketchOnPlaneAndBackSideTest = async ( | ||||
|     page: any, | ||||
|     page: Page, | ||||
|     homePage: HomePageFixture, | ||||
|     plane: string, | ||||
|     clickCoords: { x: number; y: number } | ||||
|   ) => { | ||||
|     const u = await getUtils(page) | ||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     const coord = | ||||
| @ -83,32 +77,39 @@ test.describe('Can create sketches on all planes and their back sides', () => { | ||||
|     await u.clearCommandLogs() | ||||
|     await u.removeCurrentCode() | ||||
|   } | ||||
|   test('XY', async ({ page }) => { | ||||
|   test('XY', async ({ page, homePage }) => { | ||||
|     await sketchOnPlaneAndBackSideTest( | ||||
|       page, | ||||
|       homePage, | ||||
|       'XY', | ||||
|       { 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. | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('YZ', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, 'YZ', { x: 700, y: 250 }) // green plane | ||||
|   test('YZ', async ({ page, homePage }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, 'YZ', { x: 700, y: 250 }) // green plane | ||||
|   }) | ||||
|  | ||||
|   test('XZ', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane | ||||
|   test('XZ', async ({ page, homePage }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, '-XZ', { x: 700, y: 80 }) // blue plane | ||||
|   }) | ||||
|  | ||||
|   test('-XY', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, '-XY', { x: 600, y: 118 }) // back of red plane | ||||
|   test('-XY', async ({ page, homePage }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, '-XY', { | ||||
|       x: 600, | ||||
|       y: 118, | ||||
|     }) // back of red plane | ||||
|   }) | ||||
|  | ||||
|   test('-YZ', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, '-YZ', { x: 700, y: 219 }) // back of green plane | ||||
|   test('-YZ', async ({ page, homePage }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, '-YZ', { | ||||
|       x: 700, | ||||
|       y: 219, | ||||
|     }) // back of green plan | ||||
|   }) | ||||
|  | ||||
|   test('-XZ', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane | ||||
|   test('-XZ', async ({ page, homePage }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, homePage, 'XZ', { x: 700, y: 427 }) // back of blue plane | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -1,28 +1,15 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| import { | ||||
|   getUtils, | ||||
|   setup, | ||||
|   setupElectron, | ||||
|   tearDown, | ||||
|   executorInputPath, | ||||
| } from './test-utils' | ||||
| import { getUtils, executorInputPath } from './test-utils' | ||||
| import { join } from 'path' | ||||
| import { bracket } from 'lib/exampleKcl' | ||||
| import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' | ||||
| 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('Typing KCL errors induces a badge on the code pane button', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
| @ -31,18 +18,18 @@ test.describe('Code pane and errors', () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `// Extruded Triangle | ||||
| sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([10, 0], %) | ||||
|   |> line([-5, 10], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(5, sketch001)` | ||||
|   sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([10, 0], %) | ||||
|     |> line([-5, 10], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(5, sketch001)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -64,9 +51,9 @@ extrude001 = extrude(5, sketch001)` | ||||
|  | ||||
|   test('Opening and closing the code pane will consistently show error diagnostics', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|     editor, | ||||
|   }) => { | ||||
|     await page.goto('http://localhost:3000') | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
| @ -74,8 +61,8 @@ extrude001 = extrude(5, sketch001)` | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, bracket) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 900 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 900 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -91,8 +78,9 @@ extrude001 = extrude(5, sketch001)` | ||||
|     await expect(codePaneButtonHolder).not.toContainText('notification') | ||||
|  | ||||
|     // Delete a character to break the KCL | ||||
|     await u.openKclCodePanel() | ||||
|     await page.getByText('thickness, bracketLeg1Sketch)').click() | ||||
|     await editor.openPane() | ||||
|     await editor.scrollToText('thickness, bracketLeg1Sketch)') | ||||
|     await page.getByText('extrude(thickness, bracketLeg1Sketch)').click() | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     // Ensure that a badge appears on the button | ||||
| @ -116,7 +104,10 @@ extrude001 = extrude(5, sketch001)` | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // Open the code pane | ||||
|     await u.openKclCodePanel() | ||||
|     await editor.openPane() | ||||
|  | ||||
|     // Go to our problematic code again (missing closing paren!) | ||||
|     await editor.scrollToText('extrude(thickness, bracketLeg1Sketch') | ||||
|  | ||||
|     // Ensure that a badge appears on the button | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
| @ -129,18 +120,18 @@ extrude001 = extrude(5, sketch001)` | ||||
|     await expect(page.locator('.cm-tooltip').first()).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('When error is not in view you can click the badge to scroll to it', async ({ | ||||
|   test.fixme('When error is not in view you can click the badge to scroll to it', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|     context, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
|     await page.addInitScript((code) => { | ||||
|     await context.addInitScript((code) => { | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
| @ -164,24 +155,24 @@ extrude001 = extrude(5, sketch001)` | ||||
|     await expect( | ||||
|       page | ||||
|         .getByText( | ||||
|           'sketch profile must lie entirely on one side of the revolution axis' | ||||
|           'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed:  sketch profile must lie entirely on one side of the revolution axis" }]' | ||||
|         ) | ||||
|         .first() | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ | ||||
|     context, | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
|     await page.addInitScript((code) => { | ||||
|     await context.addInitScript((code) => { | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
| @ -241,32 +232,29 @@ extrude001 = extrude(5, sketch001)` | ||||
| test( | ||||
|   'Opening multiple panes persists when switching projects', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browserName }, testInfo) => { | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     // Setup multiple projects. | ||||
|     const { electronApp, page } = await setupElectron({ | ||||
|       testInfo, | ||||
|       folderSetupFn: async (dir) => { | ||||
|         const routerTemplateDir = join(dir, 'router-template-slate') | ||||
|         const bracketDir = join(dir, 'bracket') | ||||
|         await Promise.all([ | ||||
|           fsp.mkdir(routerTemplateDir, { recursive: true }), | ||||
|           fsp.mkdir(bracketDir, { recursive: true }), | ||||
|         ]) | ||||
|         await Promise.all([ | ||||
|           fsp.copyFile( | ||||
|             executorInputPath('router-template-slate.kcl'), | ||||
|             join(routerTemplateDir, 'main.kcl') | ||||
|           ), | ||||
|           fsp.copyFile( | ||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|             join(bracketDir, 'main.kcl') | ||||
|           ), | ||||
|         ]) | ||||
|       }, | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const routerTemplateDir = join(dir, 'router-template-slate') | ||||
|       const bracketDir = join(dir, 'bracket') | ||||
|       await Promise.all([ | ||||
|         fsp.mkdir(routerTemplateDir, { recursive: true }), | ||||
|         fsp.mkdir(bracketDir, { recursive: true }), | ||||
|       ]) | ||||
|       await Promise.all([ | ||||
|         fsp.copyFile( | ||||
|           executorInputPath('router-template-slate.kcl'), | ||||
|           join(routerTemplateDir, 'main.kcl') | ||||
|         ), | ||||
|         fsp.copyFile( | ||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|           join(bracketDir, 'main.kcl') | ||||
|         ), | ||||
|       ]) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await test.step('Opening the bracket project should load', async () => { | ||||
|       await expect(page.getByText('bracket')).toBeVisible() | ||||
| @ -309,30 +297,21 @@ test( | ||||
|       await expect(page.locator('#variables-pane')).toBeVisible() | ||||
|       await expect(page.locator('#logs-pane')).toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     await electronApp.close() | ||||
|   } | ||||
| ) | ||||
|  | ||||
| test( | ||||
|   'external change of file contents are reflected in editor', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browserName }, testInfo) => { | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     const PROJECT_DIR_NAME = 'lee-was-here' | ||||
|     const { | ||||
|       electronApp, | ||||
|       page, | ||||
|       dir: projectsDir, | ||||
|     } = await setupElectron({ | ||||
|       testInfo, | ||||
|       folderSetupFn: async (dir) => { | ||||
|         const aProjectDir = join(dir, PROJECT_DIR_NAME) | ||||
|         await fsp.mkdir(aProjectDir, { recursive: true }) | ||||
|       }, | ||||
|     const { dir: projectsDir } = await context.folderSetupFn(async (dir) => { | ||||
|       const aProjectDir = join(dir, PROJECT_DIR_NAME) | ||||
|       await fsp.mkdir(aProjectDir, { recursive: true }) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await test.step('Open the project', async () => { | ||||
|       await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible() | ||||
| @ -351,7 +330,5 @@ test( | ||||
|       ) | ||||
|       await u.editorTextMatches(content) | ||||
|     }) | ||||
|  | ||||
|     await electronApp.close() | ||||
|   } | ||||
| ) | ||||
|  | ||||
| @ -1,37 +1,30 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { getUtils } from './test-utils' | ||||
| 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('Extrude from command bar selects extrude line after', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> xLine(-20, %) | ||||
|     |> close(%) | ||||
|       ` | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> xLine(-20, %) | ||||
|   |> close(%) | ||||
|     ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -52,24 +45,24 @@ test.describe('Command bar tests', () => { | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Fillet from command bar', async ({ page }) => { | ||||
|   test('Fillet from command bar', async ({ page, homePage }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-5, -5], %) | ||||
|   |> line([0, 10], %) | ||||
|   |> line([10, 0], %) | ||||
|   |> line([0, -10], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(-10, sketch001)` | ||||
|     |> startProfileAt([-5, -5], %) | ||||
|     |> line([0, 10], %) | ||||
|     |> line([10, 0], %) | ||||
|     |> line([0, -10], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(-10, sketch001)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
| @ -93,10 +86,10 @@ extrude001 = extrude(-10, sketch001)` | ||||
|  | ||||
|   test('Command bar can change a setting, and switch back and forth between arguments', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     const cmdSearchBar = page.getByPlaceholder('Search commands') | ||||
| @ -153,7 +146,7 @@ extrude001 = extrude(-10, sketch001)` | ||||
|     // Check that the visibility changed | ||||
|     await expect(paneSelector).not.toBeVisible() | ||||
|  | ||||
|     commandOptionInput = page.getByPlaceholder('off') | ||||
|     commandOptionInput = page.locator('[id="option-input"]') | ||||
|  | ||||
|     // Test case for https://github.com/KittyCAD/modeling-app/issues/2882 | ||||
|     await commandBarButton.click() | ||||
| @ -174,10 +167,10 @@ extrude001 = extrude(-10, sketch001)` | ||||
|  | ||||
|   test('Command bar keybinding works from code editor and can change a setting', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
| @ -221,25 +214,25 @@ extrude001 = extrude(-10, sketch001)` | ||||
|     await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) | ||||
|   }) | ||||
|  | ||||
|   test('Can extrude from the command bar', async ({ page }) => { | ||||
|   test('Can extrude from the command bar', async ({ page, homePage }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `distance = sqrt(20) | ||||
|       sketch001 = startSketchOn('XZ') | ||||
|       |> startProfileAt([-6.95, 10.98], %) | ||||
|       |> line([25.1, 0.41], %) | ||||
|       |> line([0.73, -20.93], %) | ||||
|       |> line([-23.44, 0.52], %) | ||||
|       |> close(%) | ||||
|           ` | ||||
|     sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([-6.95, 10.98], %) | ||||
|     |> line([25.1, 0.41], %) | ||||
|     |> line([0.73, -20.93], %) | ||||
|     |> line([-23.44, 0.52], %) | ||||
|     |> close(%) | ||||
|         ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // Make sure the stream is up | ||||
|     await u.openDebugPanel() | ||||
| @ -293,26 +286,19 @@ extrude001 = extrude(-10, sketch001)` | ||||
|     await continueButton.click() | ||||
|     await submitButton.click() | ||||
|  | ||||
|     // Check that the code was updated | ||||
|     await u.waitForCmdReceive('extrude') | ||||
|     // Unfortunately this indentation seems to matter for the test | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `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 | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toContainText( | ||||
|       'extrude001 = extrude(distance001, sketch001)' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Can switch between sketch tools via command bar', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|   test('Can switch between sketch tools via command bar', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     const sketchButton = page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     const cmdBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|  | ||||
| @ -1,23 +1,16 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { test, expect } from './zoo-test' | ||||
| import { getUtils } 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', () => { | ||||
|   // eslint-disable-next-line jest/valid-title | ||||
|   test.skip(true, 'Needs to get covered again') | ||||
|  | ||||
|   test('completes code in empty file', async ({ page }) => { | ||||
|   test('completes code in empty file', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -52,12 +45,13 @@ test.describe('Copilot ghost text', () => { | ||||
|  | ||||
|   test.skip('copilot disabled in sketch mode no select plane', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -101,12 +95,13 @@ test.describe('Copilot ghost text', () => { | ||||
|  | ||||
|   test('copilot disabled in sketch mode after selecting plane', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -184,12 +179,12 @@ test.describe('Copilot ghost text', () => { | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('ArrowUp in code rejects the suggestion', async ({ page }) => { | ||||
|   test('ArrowUp in code rejects the suggestion', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -212,12 +207,15 @@ test.describe('Copilot ghost text', () => { | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('ArrowDown in code rejects the suggestion', async ({ page }) => { | ||||
|   test('ArrowDown in code rejects the suggestion', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -240,12 +238,15 @@ test.describe('Copilot ghost text', () => { | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('ArrowLeft in code rejects the suggestion', async ({ page }) => { | ||||
|   test('ArrowLeft in code rejects the suggestion', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -268,12 +269,15 @@ test.describe('Copilot ghost text', () => { | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('ArrowRight in code rejects the suggestion', async ({ page }) => { | ||||
|   test('ArrowRight in code rejects the suggestion', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -296,12 +300,12 @@ test.describe('Copilot ghost text', () => { | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('Enter in code scoots it down', async ({ page }) => { | ||||
|   test('Enter in code scoots it down', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -326,12 +330,15 @@ test.describe('Copilot ghost text', () => { | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Ctrl+shift+z in code rejects the suggestion', async ({ page }) => { | ||||
|   test('Ctrl+shift+z in code rejects the suggestion', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
| @ -360,12 +367,13 @@ test.describe('Copilot ghost text', () => { | ||||
|  | ||||
|   test('Ctrl+z in code rejects the suggestion and undos the last code', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await page.waitForTimeout(800) | ||||
|     await u.codeLocator.click() | ||||
| @ -420,98 +428,107 @@ test.describe('Copilot ghost text', () => { | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     // 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 ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     test('delete in code rejects the suggestion', async ({ | ||||
|       page, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|       await u.codeLocator.click() | ||||
|       await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|       await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|       await page.waitForTimeout(500) | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.keyboard.press('Enter') | ||||
|       await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|       await expect(page.locator('.cm-content')).toHaveText( | ||||
|         `fn cube = (pos, scale) => {  sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|       ) | ||||
|       await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|         `fn cube = (pos, scale) => {` | ||||
|       ) | ||||
|  | ||||
|     // Going elsewhere in the code should hide the ghost text. | ||||
|     await page.keyboard.press('Delete') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|       // Going elsewhere in the code should hide the ghost text. | ||||
|       await page.keyboard.press('Delete') | ||||
|       await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|       await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|     }) | ||||
|  | ||||
|   test('backspace in code rejects the suggestion', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     test('backspace in code rejects the suggestion', async ({ | ||||
|       page, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|       await u.codeLocator.click() | ||||
|       await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|       await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|       await page.waitForTimeout(500) | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.keyboard.press('Enter') | ||||
|       await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|       await expect(page.locator('.cm-content')).toHaveText( | ||||
|         `fn cube = (pos, scale) => {  sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|       ) | ||||
|       await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|         `fn cube = (pos, scale) => {` | ||||
|       ) | ||||
|  | ||||
|     // Going elsewhere in the code should hide the ghost text. | ||||
|     await page.keyboard.press('Backspace') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|       // Going elsewhere in the code should hide the ghost text. | ||||
|       await page.keyboard.press('Backspace') | ||||
|       await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|       await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|     }) | ||||
|  | ||||
|   test('focus outside code pane rejects the suggestion', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     test('focus outside code pane rejects the suggestion', async ({ | ||||
|       page, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|       await u.codeLocator.click() | ||||
|       await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|       await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|       await page.waitForTimeout(500) | ||||
|       await page.keyboard.press('Enter') | ||||
|       await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|       await expect(page.locator('.cm-content')).toHaveText( | ||||
|         `fn cube = (pos, scale) => {  sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|       ) | ||||
|       await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|         `fn cube = (pos, scale) => {` | ||||
|       ) | ||||
|  | ||||
|     // Going outside the editor should hide the ghost text. | ||||
|     await page.mouse.move(0, 0) | ||||
|     await page | ||||
|       .getByRole('button', { name: 'Start Sketch' }) | ||||
|       .waitFor({ state: 'visible' }) | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|       // Going outside the editor should hide the ghost text. | ||||
|       await page.mouse.move(0, 0) | ||||
|       await page | ||||
|         .getByRole('button', { name: 'Start Sketch' }) | ||||
|         .waitFor({ state: 'visible' }) | ||||
|       await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|       await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|       await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -1,14 +1,6 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| 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) | ||||
| }) | ||||
| import { getUtils } from './test-utils' | ||||
|  | ||||
| function countNewlines(input: string): number { | ||||
|   let count = 0 | ||||
| @ -24,13 +16,14 @@ test.describe('Debug pane', () => { | ||||
|   test('Artifact IDs in the artifact graph are stable across code edits', async ({ | ||||
|     page, | ||||
|     context, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const code = `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
| |> line([1, 1], %) | ||||
| ` | ||||
|     |> startProfileAt([0, 0], %) | ||||
|   |> line([1, 1], %) | ||||
|   ` | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     const tree = page.getByTestId('debug-feature-tree') | ||||
|     const segment = tree.locator('li', { | ||||
| @ -39,7 +32,7 @@ test.describe('Debug pane', () => { | ||||
|     }) | ||||
|  | ||||
|     await test.step('Test setup', async () => { | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.openKclCodePanel() | ||||
|       await u.openDebugPanel() | ||||
|       // Set the code in the code editor. | ||||
|  | ||||
| @ -1,39 +1,31 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { join } from 'path' | ||||
| import { test, expect } from './zoo-test' | ||||
| import path from 'path' | ||||
| import { | ||||
|   getUtils, | ||||
|   setupElectron, | ||||
|   tearDown, | ||||
|   executorInputPath, | ||||
|   getPlaywrightDownloadDir, | ||||
| } from './test-utils' | ||||
| import fsp from 'fs/promises' | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test( | ||||
|   'export works on the first try', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browserName }, testInfo) => { | ||||
|     const { electronApp, page } = await setupElectron({ | ||||
|       testInfo, | ||||
|       folderSetupFn: async (dir) => { | ||||
|         const bracketDir = join(dir, 'bracket') | ||||
|         await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) | ||||
|         await Promise.all([ | ||||
|           fsp.copyFile( | ||||
|             executorInputPath('router-template-slate.kcl'), | ||||
|             join(bracketDir, 'other.kcl') | ||||
|           ), | ||||
|           fsp.copyFile( | ||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|             join(bracketDir, 'main.kcl') | ||||
|           ), | ||||
|         ]) | ||||
|       }, | ||||
|   async ({ page, context }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const bracketDir = path.join(dir, 'bracket') | ||||
|       await Promise.all([fsp.mkdir(bracketDir, { recursive: true })]) | ||||
|       await Promise.all([ | ||||
|         fsp.copyFile( | ||||
|           executorInputPath('router-template-slate.kcl'), | ||||
|           path.join(bracketDir, 'other.kcl') | ||||
|         ), | ||||
|         fsp.copyFile( | ||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|           path.join(bracketDir, 'main.kcl') | ||||
|         ), | ||||
|       ]) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     page.on('console', console.log) | ||||
|  | ||||
| @ -93,12 +85,16 @@ test( | ||||
|       await expect(successToastMessage).toBeVisible() | ||||
|       await expect(exportingToastMessage).not.toBeVisible() | ||||
|  | ||||
|       const firstFileFullPath = path.resolve( | ||||
|         getPlaywrightDownloadDir(page), | ||||
|         exportFileName | ||||
|       ) | ||||
|       await test.step('Check the export size', async () => { | ||||
|         await expect | ||||
|           .poll( | ||||
|             async () => { | ||||
|               try { | ||||
|                 const outputGltf = await fsp.readFile(exportFileName) | ||||
|                 const outputGltf = await fsp.readFile(firstFileFullPath) | ||||
|                 return outputGltf.byteLength | ||||
|               } catch (e) { | ||||
|                 return 0 | ||||
| @ -107,9 +103,6 @@ test( | ||||
|             { timeout: 15_000 } | ||||
|           ) | ||||
|           .toBeGreaterThan(300_000) | ||||
|  | ||||
|         // clean up exported file | ||||
|         await fsp.rm(exportFileName) | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
| @ -170,12 +163,16 @@ test( | ||||
|           expect(exportingToastMessage).not.toBeVisible(), | ||||
|         ])) | ||||
|  | ||||
|       const secondFileFullPath = path.resolve( | ||||
|         getPlaywrightDownloadDir(page), | ||||
|         exportFileName | ||||
|       ) | ||||
|       await test.step('Check the export size', async () => { | ||||
|         await expect | ||||
|           .poll( | ||||
|             async () => { | ||||
|               try { | ||||
|                 const outputGltf = await fsp.readFile(exportFileName) | ||||
|                 const outputGltf = await fsp.readFile(secondFileFullPath) | ||||
|                 return outputGltf.byteLength | ||||
|               } catch (e) { | ||||
|                 return 0 | ||||
| @ -184,13 +181,7 @@ test( | ||||
|             { timeout: 15_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 '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
| import fsp from 'fs/promises' | ||||
| import { uuidv4 } from 'lib/utils' | ||||
| import { | ||||
| @ -6,37 +6,27 @@ import { | ||||
|   darkModePlaneColorXZ, | ||||
|   executorInputPath, | ||||
|   getUtils, | ||||
|   setup, | ||||
|   setupElectron, | ||||
|   tearDown, | ||||
| } from './test-utils' | ||||
|  | ||||
| import { join } from 'path' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.describe('Editor tests', () => { | ||||
|   test('can comment out code with ctrl+/', async ({ page }) => { | ||||
|   test('can comment out code with ctrl+/', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type(`sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|  | ||||
|     await page.keyboard.down('ControlOrMeta') | ||||
|     await page.keyboard.press('/') | ||||
| @ -44,11 +34,11 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     // |> close(%)`) | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   // |> close(%)`) | ||||
|  | ||||
|     // uncomment the code | ||||
|     await page.keyboard.down('ControlOrMeta') | ||||
| @ -57,61 +47,63 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   test('if you click the format button it formats your code', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type(`sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|     await page.locator('#code-pane button:first-child').click() | ||||
|     await page.locator('button:has-text("Format code")').click() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|     await page.locator('#code-pane button:first-child').click() | ||||
|     await page.locator('button:has-text("Format code")').click() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   test('if you click the format button it formats your code and executes so lints are still there', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type(`sketch_001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -135,11 +127,11 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch_001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|  | ||||
|     // error in guter | ||||
|     await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() | ||||
| @ -151,29 +143,27 @@ test.describe('Editor tests', () => { | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('fold gutters work', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|   test('fold gutters work', async ({ page, homePage }) => { | ||||
|     const fullCode = `sketch001 = startSketchOn('XY') | ||||
|      |> startProfileAt([-10, -10], %) | ||||
|      |> line([20, 0], %) | ||||
|      |> line([0, 20], %) | ||||
|      |> line([-20, 0], %) | ||||
|      |> close(%)` | ||||
|    |> startProfileAt([-10, -10], %) | ||||
|    |> line([20, 0], %) | ||||
|    |> line([0, 20], %) | ||||
|    |> line([-20, 0], %) | ||||
|    |> close(%)` | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|      |> startProfileAt([-10, -10], %) | ||||
|      |> line([20, 0], %) | ||||
|      |> line([0, 20], %) | ||||
|      |> line([-20, 0], %) | ||||
|      |> close(%)` | ||||
|    |> startProfileAt([-10, -10], %) | ||||
|    |> line([20, 0], %) | ||||
|    |> line([0, 20], %) | ||||
|    |> line([-20, 0], %) | ||||
|    |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // TODO: Jess needs to fix this but you have to mod the code to get them to show | ||||
|     // up, its an annoying codemirror thing. | ||||
| @ -224,22 +214,25 @@ test.describe('Editor tests', () => { | ||||
|     await expect(foldGutterFoldLine).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('hover over functions shows function description', async ({ page }) => { | ||||
|   test('hover over functions shows function description', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)` | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
| @ -268,23 +261,24 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|   test('if you use the format keyboard binding it formats your code', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)` | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)` | ||||
|       ) | ||||
|       localStorage.setItem('disableAxis', 'true') | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
| @ -301,32 +295,33 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   test('if you use the format keyboard binding it formats your code and executes so lints are shown', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch_001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)` | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)` | ||||
|       ) | ||||
|       localStorage.setItem('disableAxis', 'true') | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -353,11 +348,11 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch_001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|  | ||||
|     // error in guter | ||||
|     await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() | ||||
| @ -369,11 +364,14 @@ test.describe('Editor tests', () => { | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('if you write kcl with lint errors you get lints', async ({ page }) => { | ||||
|   test('if you write kcl with lint errors you get lints', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() | ||||
| @ -409,23 +407,26 @@ test.describe('Editor tests', () => { | ||||
|     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('if you fixup kcl errors you clear lints', async ({ page }) => { | ||||
|   test('if you fixup kcl errors you clear lints', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([3.29, 7.86], %) | ||||
|   |> line([2.48, 2.44], %) | ||||
|   |> line([2.66, 1.17], %) | ||||
|   |> close(%) | ||||
|   ` | ||||
|     |> startProfileAt([3.29, 7.86], %) | ||||
|     |> line([2.48, 2.44], %) | ||||
|     |> line([2.66, 1.17], %) | ||||
|     |> close(%) | ||||
|     ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
| @ -447,20 +448,23 @@ test.describe('Editor tests', () => { | ||||
|     ).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('if you write invalid kcl you get inlined errors', async ({ page }) => { | ||||
|   test('if you write invalid kcl you get inlined errors', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     /* add the following code to the editor ($ error is not a valid line) | ||||
|       $ error | ||||
|       const topAng = 30 | ||||
|       const bottomAng = 25 | ||||
|      */ | ||||
|     $ error | ||||
|     const topAng = 30 | ||||
|     const bottomAng = 25 | ||||
|    */ | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type('$ error') | ||||
|  | ||||
| @ -518,34 +522,36 @@ test.describe('Editor tests', () => { | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   // TODO currently multiple source ranges are not supported | ||||
|   test.skip('error with 2 source ranges gets 2 diagnostics', async ({ | ||||
|   test.fixme('error with 2 source ranges gets 2 diagnostics', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `length = .750 | ||||
|   width = 0.500 | ||||
|   height = 0.500 | ||||
|   dia = 4 | ||||
|  | ||||
|   fn squareHole = (l, w) => { | ||||
|     squareHoleSketch = startSketchOn('XY') | ||||
|     |> startProfileAt([-width / 2, -length / 2], %) | ||||
|     |> lineTo([width / 2, -length / 2], %) | ||||
|     |> lineTo([width / 2, length / 2], %) | ||||
|     |> lineTo([-width / 2, length / 2], %) | ||||
|     |> close(%) | ||||
|     return squareHoleSketch | ||||
|   } | ||||
|   ` | ||||
|     width = 0.500 | ||||
|     height = 0.500 | ||||
|     dia = 4 | ||||
|    | ||||
|     fn squareHole = (l, w) => { | ||||
|   squareHoleSketch = startSketchOn('XY') | ||||
|   |> startProfileAt([-width / 2, -length / 2], %) | ||||
|   |> lineTo([width / 2, -length / 2], %) | ||||
|   |> lineTo([width / 2, length / 2], %) | ||||
|   |> lineTo([-width / 2, length / 2], %) | ||||
|   |> close(%) | ||||
|   return squareHoleSketch | ||||
|     } | ||||
|     ` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -571,9 +577,9 @@ test.describe('Editor tests', () => { | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type(`extrusion = startSketchOn('XY') | ||||
|     |> circle({ center = [0, 0], radius = dia/2 }, %) | ||||
|   |> hole(squareHole(length, width, height), %) | ||||
|   |> extrude(height, %)`) | ||||
|   |> circle({ center: [0, 0], radius: dia/2 }, %) | ||||
|     |> hole(squareHole(length, width, height), %) | ||||
|     |> extrude(height, %)`) | ||||
|  | ||||
|     // error in gutter | ||||
|     await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() | ||||
| @ -586,38 +592,38 @@ test.describe('Editor tests', () => { | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2) | ||||
|   }) | ||||
|   test('if your kcl gets an error from the engine it is inlined', async ({ | ||||
|     context, | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|     await context.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `box = startSketchOn('XY') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([0, 10], %) | ||||
|   |> line([10, 0], %) | ||||
|   |> line([0, -10], %, $revolveAxis) | ||||
|   |> close(%) | ||||
|   |> extrude(10, %) | ||||
|  | ||||
|   sketch001 = startSketchOn(box, revolveAxis) | ||||
|   |> startProfileAt([5, 10], %) | ||||
|   |> line([0, -10], %) | ||||
|   |> line([2, 0], %) | ||||
|   |> line([0, -10], %) | ||||
|   |> close(%) | ||||
|   |> revolve({ | ||||
|   axis = revolveAxis, | ||||
|   angle = 90 | ||||
|   }, %) | ||||
|       ` | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([0, 10], %) | ||||
|     |> line([10, 0], %) | ||||
|     |> line([0, -10], %, $revolveAxis) | ||||
|     |> close(%) | ||||
|     |> extrude(10, %) | ||||
|    | ||||
|     sketch001 = startSketchOn(box, revolveAxis) | ||||
|     |> startProfileAt([5, 10], %) | ||||
|     |> line([0, -10], %) | ||||
|     |> line([2, 0], %) | ||||
|     |> line([0, -10], %) | ||||
|     |> close(%) | ||||
|     |> revolve({ | ||||
|     axis: revolveAxis, | ||||
|     angle: 90 | ||||
|     }, %) | ||||
|     ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await page.goto('/') | ||||
|     await u.waitForPageLoad() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|  | ||||
| @ -628,12 +634,15 @@ test.describe('Editor tests', () => { | ||||
|     await expect(page.getByText(searchText)).toBeVisible() | ||||
|   }) | ||||
|   test.describe('Autocomplete works', () => { | ||||
|     test('with enter/click to accept the completion', async ({ page }) => { | ||||
|     test('with enter/click to accept the completion', async ({ | ||||
|       page, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // tests clicking on an option, selection the first option | ||||
|       // and arrowing down to an option | ||||
| @ -695,19 +704,19 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([3.14, 12], %) | ||||
|     |> xLine(5, %) // lin`) | ||||
|         |> startProfileAt([3.14, 12], %) | ||||
|         |> xLine(5, %) // lin`) | ||||
|  | ||||
|       // expect there to be no KCL errors | ||||
|       await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) | ||||
|     }) | ||||
|  | ||||
|     test('with tab to accept the completion', async ({ page }) => { | ||||
|     test('with tab to accept the completion', async ({ page, homePage }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // this test might be brittle as we add and remove functions | ||||
|       // but should also be easy to update. | ||||
| @ -769,26 +778,30 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([3.14, 12], %) | ||||
|     |> xLine(5, %) // lin`) | ||||
|         |> startProfileAt([3.14, 12], %) | ||||
|         |> xLine(5, %) // lin`) | ||||
|     }) | ||||
|   }) | ||||
|   test('Can undo a click and point extrude with ctrl+z', async ({ page }) => { | ||||
|   test('Can undo a click and point extrude with ctrl+z', async ({ | ||||
|     page, | ||||
|     context, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|     await context.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%)` | ||||
|   |> startProfileAt([4.61, -14.01], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -5.38], %) | ||||
|   |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
| @ -841,29 +854,32 @@ test.describe('Editor tests', () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%)`) | ||||
|   |> startProfileAt([4.61, -14.01], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -5.38], %) | ||||
|   |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   test('Can undo a sketch modification with ctrl+z', async ({ page }) => { | ||||
|   test('Can undo a sketch modification with ctrl+z', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -10.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -0.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)` | ||||
|   |> startProfileAt([4.61, -10.01], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -0.38], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
| @ -890,7 +906,7 @@ test.describe('Editor tests', () => { | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     const startPX = [665, 397] | ||||
|     const startPX = [1200 / 2, 500 / 2] | ||||
|  | ||||
|     const dragPX = 40 | ||||
|  | ||||
| @ -904,9 +920,9 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.getByTestId('segment-overlay')).toHaveCount(2) | ||||
|  | ||||
|     // drag startProfieAt handle | ||||
|     // drag startProfileAt handle | ||||
|     await page.dragAndDrop('#stream', '#stream', { | ||||
|       sourcePosition: { x: startPX[0], y: startPX[1] }, | ||||
|       sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 }, | ||||
|       targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
| @ -944,12 +960,12 @@ test.describe('Editor tests', () => { | ||||
|     // expect the code to have changed | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([7.12, -12.68], %) | ||||
|   |> line([15.39, -2.78], %) | ||||
|   |> tangentialArcTo([27.6, -3.05], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %) | ||||
| `) | ||||
|     |> startProfileAt([2.71, -2.71], %) | ||||
|     |> line([15.4, -2.78], %) | ||||
|     |> tangentialArcTo([27.6, -3.05], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %) | ||||
|   `) | ||||
|  | ||||
|     // Hit undo | ||||
|     await page.keyboard.down('Control') | ||||
| @ -958,11 +974,11 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([7.12, -12.68], %) | ||||
|   |> line([15.39, -2.78], %) | ||||
|   |> tangentialArcTo([24.95, -0.38], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %)`) | ||||
|     |> startProfileAt([2.71, -2.71], %) | ||||
|     |> line([15.4, -2.78], %) | ||||
|     |> tangentialArcTo([24.95, -0.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)`) | ||||
|  | ||||
|     // Hit undo again. | ||||
|     await page.keyboard.down('Control') | ||||
| @ -971,12 +987,12 @@ test.describe('Editor tests', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([7.12, -12.68], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -0.38], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %) | ||||
| `) | ||||
|     |> startProfileAt([2.71, -2.71], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -0.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %) | ||||
|   `) | ||||
|  | ||||
|     // Hit undo again. | ||||
|     await page.keyboard.down('Control') | ||||
| @ -986,31 +1002,29 @@ test.describe('Editor tests', () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -10.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -0.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)`) | ||||
|   |> startProfileAt([4.61, -10.01], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -0.38], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %)`) | ||||
|   }) | ||||
|  | ||||
|   test.fixme( | ||||
|     `Can use the import stdlib function on a local OBJ file`, | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async (dir) => { | ||||
|           const bracketDir = join(dir, 'cube') | ||||
|           await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|           await fsp.copyFile( | ||||
|             executorInputPath('cube.obj'), | ||||
|             join(bracketDir, 'cube.obj') | ||||
|           ) | ||||
|           await fsp.writeFile(join(bracketDir, 'main.kcl'), '') | ||||
|         }, | ||||
|     async ({ page, context }, testInfo) => { | ||||
|       await context.folderSetupFn(async (dir) => { | ||||
|         const bracketDir = join(dir, 'cube') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('cube.obj'), | ||||
|           join(bracketDir, 'cube.obj') | ||||
|         ) | ||||
|         await fsp.writeFile(join(bracketDir, 'main.kcl'), '') | ||||
|       }) | ||||
|  | ||||
|       const viewportSize = { width: 1200, height: 500 } | ||||
|       await page.setViewportSize(viewportSize) | ||||
|       await page.setBodyDimensions(viewportSize) | ||||
|  | ||||
|       // Locators and constants | ||||
|       const u = await getUtils(page) | ||||
| @ -1068,8 +1082,6 @@ test.describe('Editor tests', () => { | ||||
|           }) | ||||
|           .toBeGreaterThan(15) | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| @ -54,13 +54,13 @@ export class EditorFixture { | ||||
|         } | ||||
|       } | ||||
|       if (!shouldNormalise) { | ||||
|         const expectStart = expect(this.codeContent) | ||||
|         const expectStart = expect.poll(() => this.codeContent.textContent()) | ||||
|         if (not) { | ||||
|           const result = await expectStart.not.toContainText(code, { timeout }) | ||||
|           const result = await expectStart.not.toContain(code) | ||||
|           await resetPane() | ||||
|           return result | ||||
|         } | ||||
|         const result = await expectStart.toContainText(code, { timeout }) | ||||
|         const result = await expectStart.toContain(code) | ||||
|         await resetPane() | ||||
|         return result | ||||
|       } | ||||
| @ -147,4 +147,20 @@ export class EditorFixture { | ||||
|   openPane() { | ||||
|     return openPane(this.page, this.paneButtonTestId) | ||||
|   } | ||||
|   scrollToText(text: string) { | ||||
|     return this.page.evaluate((scrollToText: string) => { | ||||
|       // editorManager is available on the window object. | ||||
|       // @ts-ignore | ||||
|       let index = editorManager._editorView.docView.view.state.doc | ||||
|         .toString() | ||||
|         .indexOf(scrollToText) | ||||
|       // @ts-ignore | ||||
|       editorManager._editorView.dispatch({ | ||||
|         selection: { | ||||
|           anchor: index, | ||||
|         }, | ||||
|         scrollIntoView: true, | ||||
|       }) | ||||
|     }, text) | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| import type { | ||||
|   BrowserContext, | ||||
|   ElectronApplication, | ||||
|   Page, | ||||
|   TestInfo, | ||||
|   Page, | ||||
| } from '@playwright/test' | ||||
| import { test as base } from '@playwright/test' | ||||
| import { getUtils, setup, setupElectron, tearDown } from '../test-utils' | ||||
|  | ||||
| import { getUtils, setup, setupElectron } from '../test-utils' | ||||
| import fsp from 'fs/promises' | ||||
| import { join } from 'path' | ||||
| import { CmdBarFixture } from './cmdBarFixture' | ||||
| @ -20,11 +20,11 @@ export class AuthenticatedApp { | ||||
|   public readonly page: Page | ||||
|   public readonly context: BrowserContext | ||||
|   public readonly testInfo: TestInfo | ||||
|   public readonly viewPortSize = { width: 1000, height: 500 } | ||||
|   public readonly viewPortSize = { width: 1200, height: 500 } | ||||
|  | ||||
|   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { | ||||
|     this.page = page | ||||
|     this.context = context | ||||
|     this.page = page | ||||
|     this.testInfo = testInfo | ||||
|   } | ||||
|  | ||||
| @ -49,9 +49,7 @@ export class AuthenticatedApp { | ||||
|   } | ||||
| } | ||||
|  | ||||
| interface Fixtures { | ||||
|   app: AuthenticatedApp | ||||
|   tronApp: AuthenticatedTronApp | ||||
| export interface Fixtures { | ||||
|   cmdBar: CmdBarFixture | ||||
|   editor: EditorFixture | ||||
|   toolbar: ToolbarFixture | ||||
| @ -61,9 +59,11 @@ interface Fixtures { | ||||
| export class AuthenticatedTronApp { | ||||
|   public readonly _page: Page | ||||
|   public page: Page | ||||
|   public readonly context: BrowserContext | ||||
|   public context: BrowserContext | ||||
|   public readonly testInfo: TestInfo | ||||
|   public electronApp?: ElectronApplication | ||||
|   public readonly viewPortSize = { width: 1200, height: 500 } | ||||
|   public dir: string = '' | ||||
|  | ||||
|   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { | ||||
|     this._page = page | ||||
| @ -79,15 +79,24 @@ export class AuthenticatedTronApp { | ||||
|       appSettings?: Partial<SaveSettingsPayload> | ||||
|     } = { fixtures: {} } | ||||
|   ) { | ||||
|     const { electronApp, page } = await setupElectron({ | ||||
|     const { electronApp, page, context, dir } = await setupElectron({ | ||||
|       testInfo: this.testInfo, | ||||
|       folderSetupFn: arg.folderSetupFn, | ||||
|       cleanProjectDir: arg.cleanProjectDir, | ||||
|       appSettings: arg.appSettings, | ||||
|     }) | ||||
|     this.page = page | ||||
|     this.context = context | ||||
|     this.electronApp = electronApp | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     this.dir = dir | ||||
|  | ||||
|     // Easier to access throughout utils | ||||
|     this.page.dir = dir | ||||
|  | ||||
|     // Setup localStorage, addCookies, reload | ||||
|     await setup(this.context, this.page, this.testInfo) | ||||
|  | ||||
|     await page.setViewportSize(this.viewPortSize) | ||||
|  | ||||
|     for (const key of unsafeTypedKeys(arg.fixtures)) { | ||||
|       const fixture = arg.fixtures[key] | ||||
| @ -110,32 +119,20 @@ export class AuthenticatedTronApp { | ||||
|     }) | ||||
| } | ||||
|  | ||||
| export const test = base.extend<Fixtures>({ | ||||
|   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) => { | ||||
| export const fixtures = { | ||||
|   cmdBar: async ({ page }: { page: Page }, use: any) => { | ||||
|     await use(new CmdBarFixture(page)) | ||||
|   }, | ||||
|   editor: async ({ page }, use) => { | ||||
|   editor: async ({ page }: { page: Page }, use: any) => { | ||||
|     await use(new EditorFixture(page)) | ||||
|   }, | ||||
|   toolbar: async ({ page }, use) => { | ||||
|   toolbar: async ({ page }: { page: Page }, use: any) => { | ||||
|     await use(new ToolbarFixture(page)) | ||||
|   }, | ||||
|   scene: async ({ page }, use) => { | ||||
|   scene: async ({ page }: { page: Page }, use: any) => { | ||||
|     await use(new SceneFixture(page)) | ||||
|   }, | ||||
|   homePage: async ({ page }, use) => { | ||||
|   homePage: async ({ page }: { page: Page }, use: any) => { | ||||
|     await use(new HomePageFixture(page)) | ||||
|   }, | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| export { expect } from '@playwright/test' | ||||
| } | ||||
|  | ||||
| @ -14,10 +14,14 @@ interface HomePageState { | ||||
| export class HomePageFixture { | ||||
|   public page: Page | ||||
|  | ||||
|   projectSection!: Locator | ||||
|   projectCard!: Locator | ||||
|   projectCardTitle!: Locator | ||||
|   projectCardFile!: Locator | ||||
|   projectCardFolder!: Locator | ||||
|   projectButtonNew!: Locator | ||||
|   projectButtonContinue!: Locator | ||||
|   projectTextName!: Locator | ||||
|   sortByDateBtn!: Locator | ||||
|   sortByNameBtn!: Locator | ||||
|  | ||||
| @ -28,11 +32,19 @@ export class HomePageFixture { | ||||
|   reConstruct = (page: Page) => { | ||||
|     this.page = page | ||||
|  | ||||
|     this.projectSection = this.page.getByTestId('home-section') | ||||
|  | ||||
|     this.projectCard = this.page.getByTestId('project-link') | ||||
|     this.projectCardTitle = this.page.getByTestId('project-title') | ||||
|     this.projectCardFile = this.page.getByTestId('project-file-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.sortByNameBtn = this.page.getByTestId('home-sort-by-name') | ||||
|   } | ||||
| @ -91,10 +103,25 @@ export class HomePageFixture { | ||||
|       .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) => { | ||||
|     const projectCard = this.projectCard.locator( | ||||
|       this.page.getByText(projectTitle) | ||||
|     ) | ||||
|     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,8 +53,9 @@ export class SceneFixture { | ||||
|  | ||||
|   expectState = async (expected: SceneSerialised) => { | ||||
|     return expect | ||||
|       .poll(() => this._serialiseScene(), { | ||||
|         message: `Expected scene state to match`, | ||||
|       .poll(async () => await this._serialiseScene(), { | ||||
|         intervals: [1_000, 2_000, 10_000], | ||||
|         timeout: 60000, | ||||
|       }) | ||||
|       .toEqual(expected) | ||||
|   } | ||||
| @ -187,7 +188,10 @@ export class SceneFixture { | ||||
|         type: 'default_camera_get_settings', | ||||
|       }, | ||||
|     }) | ||||
|     await this.waitForExecutionDone() | ||||
|     await this.page | ||||
|       .locator(`[data-receive-command-type="default_camera_get_settings"]`) | ||||
|       .first() | ||||
|       .waitFor() | ||||
|     const position = await Promise.all([ | ||||
|       this.page.getByTestId('cam-x-position').inputValue().then(Number), | ||||
|       this.page.getByTestId('cam-y-position').inputValue().then(Number), | ||||
| @ -238,6 +242,7 @@ export class SceneFixture { | ||||
|   } | ||||
|  | ||||
|   async clickGizmoMenuItem(name: string) { | ||||
|     await this.gizmo.hover() | ||||
|     await this.gizmo.click({ button: 'right' }) | ||||
|     const buttonToTest = this.page.getByRole('button', { | ||||
|       name: name, | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import type { Page, Locator } from '@playwright/test' | ||||
| import { expect } from './fixtureSetup' | ||||
| import { expect } from '../zoo-test' | ||||
| import { doAndWaitForImageDiff } from '../test-utils' | ||||
|  | ||||
| export class ToolbarFixture { | ||||
|  | ||||
| @ -1,29 +1,22 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { setupElectron, tearDown, executorInputPath } from './test-utils' | ||||
| import { test, expect } from './zoo-test' | ||||
| import { executorInputPath } from './test-utils' | ||||
| import { join } from 'path' | ||||
| import fsp from 'fs/promises' | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test( | ||||
|   'When machine-api server not found butt is disabled and shows the reason', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browserName }, testInfo) => { | ||||
|     const { electronApp, page } = await setupElectron({ | ||||
|       testInfo, | ||||
|       folderSetupFn: async (dir) => { | ||||
|         const bracketDir = join(dir, 'bracket') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|           join(bracketDir, 'main.kcl') | ||||
|         ) | ||||
|       }, | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const bracketDir = join(dir, 'bracket') | ||||
|       await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|       await fsp.copyFile( | ||||
|         executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|         join(bracketDir, 'main.kcl') | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await expect(page.getByText('bracket')).toBeVisible() | ||||
|  | ||||
| @ -47,28 +40,23 @@ test( | ||||
|     // that the machine-api server is not found | ||||
|     await makeButton.hover() | ||||
|     await expect(page.getByText(notFoundText).first()).toBeVisible() | ||||
|  | ||||
|     await electronApp.close() | ||||
|   } | ||||
| ) | ||||
|  | ||||
| test( | ||||
|   'When machine-api server not found home screen & project status shows the reason', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browserName }, testInfo) => { | ||||
|     const { electronApp, page } = await setupElectron({ | ||||
|       testInfo, | ||||
|       folderSetupFn: async (dir) => { | ||||
|         const bracketDir = join(dir, 'bracket') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|           join(bracketDir, 'main.kcl') | ||||
|         ) | ||||
|       }, | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const bracketDir = join(dir, 'bracket') | ||||
|       await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|       await fsp.copyFile( | ||||
|         executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|         join(bracketDir, 'main.kcl') | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     const notFoundText = 'Machine API server was not discovered' | ||||
|  | ||||
| @ -91,7 +79,5 @@ test( | ||||
|  | ||||
|     await networkMachineToggle.hover() | ||||
|     await expect(page.getByText(notFoundText).nth(1)).toBeVisible() | ||||
|  | ||||
|     await electronApp.close() | ||||
|   } | ||||
| ) | ||||
|  | ||||
							
								
								
									
										12
									
								
								e2e/playwright/null.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | ||||
| // 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,79 +1,63 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
| import { join } from 'path' | ||||
| import fsp from 'fs/promises' | ||||
| import { | ||||
|   getUtils, | ||||
|   setup, | ||||
|   setupElectron, | ||||
|   tearDown, | ||||
|   executorInputPath, | ||||
|   createProject, | ||||
| } from './test-utils' | ||||
| import { getUtils, executorInputPath, createProject } from './test-utils' | ||||
| import { bracket } from 'lib/exampleKcl' | ||||
| import { onboardingPaths } from 'routes/Onboarding/paths' | ||||
| import { | ||||
|   TEST_SETTINGS_KEY, | ||||
|   TEST_SETTINGS_ONBOARDING_START, | ||||
|   TEST_SETTINGS_ONBOARDING_EXPORT, | ||||
|   TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING, | ||||
|   TEST_SETTINGS_ONBOARDING_USER_MENU, | ||||
| } from './storageStates' | ||||
| import * as TOML from '@iarna/toml' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   if (testInfo.tags.includes('@electron')) { | ||||
|     return | ||||
|   } | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
| // Because onboarding relies on an app setting we need to set it as incompletel | ||||
| // for all these tests. | ||||
|  | ||||
| test.describe('Onboarding tests', () => { | ||||
|   test('Onboarding code is shown in the editor', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // 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) | ||||
|   test( | ||||
|     'Onboarding code is shown in the editor', | ||||
|     { | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: 'incomplete', | ||||
|         }, | ||||
|       }, | ||||
|       { settingsKey: TEST_SETTINGS_KEY } | ||||
|     ) | ||||
|       cleanProjectDir: true, | ||||
|     }, | ||||
|     async ({ context, page, homePage }) => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       // Test that the onboarding pane loaded | ||||
|       await expect( | ||||
|         page.getByText('Welcome to Modeling App! This') | ||||
|       ).toBeVisible() | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // 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 | ||||
|     await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') | ||||
|   }) | ||||
|       // *and* that the code is shown in the editor | ||||
|       await expect(page.locator('.cm-content')).toContainText( | ||||
|         '// Shelf Bracket' | ||||
|       ) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     'Desktop: fresh onboarding executes and loads', | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName: _ }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         appSettings: { | ||||
|           app: { | ||||
|             onboardingStatus: 'incomplete', | ||||
|           }, | ||||
|     { | ||||
|       tag: '@electron', | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: 'incomplete', | ||||
|         }, | ||||
|         cleanProjectDir: true, | ||||
|       }) | ||||
|  | ||||
|       }, | ||||
|       cleanProjectDir: true, | ||||
|     }, | ||||
|     async ({ page, homePage }, testInfo) => { | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
|       const viewportSize = { width: 1200, height: 500 } | ||||
|       await page.setViewportSize(viewportSize) | ||||
|       await page.setBodyDimensions(viewportSize) | ||||
|  | ||||
|       await test.step(`Create a project and open to the onboarding`, async () => { | ||||
|         await createProject({ name: 'project-link', page }) | ||||
| @ -93,320 +77,361 @@ test.describe('Onboarding tests', () => { | ||||
|           '// Shelf Bracket' | ||||
|         ) | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test('Code resets after confirmation', async ({ page }) => { | ||||
|     const initialCode = `sketch001 = startSketchOn('XZ')` | ||||
|  | ||||
|     // Load the page up with some code so we see the confirmation warning | ||||
|     // when we go to replay onboarding | ||||
|     await page.addInitScript((code) => { | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, initialCode) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // Replay the onboarding | ||||
|     await page.getByRole('link', { name: 'Settings' }).last().click() | ||||
|     const replayButton = page.getByRole('button', { name: 'Replay onboarding' }) | ||||
|     await expect(replayButton).toBeVisible() | ||||
|     await replayButton.click() | ||||
|  | ||||
|     // Ensure we see the warning, and that the code has not yet updated | ||||
|     await expect( | ||||
|       page.getByText('Replaying onboarding resets your code') | ||||
|     ).toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(initialCode) | ||||
|  | ||||
|     const nextButton = page.getByTestId('onboarding-next') | ||||
|     await expect(nextButton).toBeVisible() | ||||
|     await nextButton.click() | ||||
|  | ||||
|     // Ensure we see the introduction and that the code has been reset | ||||
|     await expect(page.getByText('Welcome to Modeling App!')).toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') | ||||
|  | ||||
|     // Ensure we persisted the code to local storage. | ||||
|     // Playwright's addInitScript method unfortunately will reset | ||||
|     // 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) | ||||
|  | ||||
|     // Override beforeEach test setup | ||||
|     await page.addInitScript( | ||||
|       async ({ settingsKey, settings }) => { | ||||
|         // Give no initial code, so that the onboarding start is shown immediately | ||||
|         localStorage.setItem('persistCode', '') | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|   test( | ||||
|     'Code resets after confirmation', | ||||
|     { | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: 'incomplete', | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_START }), | ||||
|       } | ||||
|     ) | ||||
|       cleanProjectDir: true, | ||||
|     }, | ||||
|     async ({ context, page, homePage }) => { | ||||
|       const initialCode = `sketch001 = startSketchOn('XZ')` | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 1080 }) | ||||
|       // Load the page up with some code so we see the confirmation warning | ||||
|       // when we go to replay onboarding | ||||
|       await context.addInitScript((code) => { | ||||
|         localStorage.setItem('persistCode', code) | ||||
|       }, initialCode) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|     // Test that the onboarding pane loaded | ||||
|     await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible() | ||||
|       // Replay the onboarding | ||||
|       await page.getByRole('link', { name: 'Settings' }).last().click() | ||||
|       const replayButton = page.getByRole('button', { | ||||
|         name: 'Replay onboarding', | ||||
|       }) | ||||
|       await expect(replayButton).toBeVisible() | ||||
|       await replayButton.click() | ||||
|  | ||||
|     const nextButton = page.getByTestId('onboarding-next') | ||||
|       // 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.locator('.cm-content')).toHaveText(initialCode) | ||||
|  | ||||
|     while ((await nextButton.innerText()) !== 'Finish') { | ||||
|       await expect(nextButton).toBeVisible() | ||||
|       const nextButton = page.getByTestId('onboarding-next') | ||||
|       await nextButton.hover() | ||||
|       await nextButton.click() | ||||
|  | ||||
|       // Ensure we see the introduction and that the code has been reset | ||||
|       await expect(page.getByText('Welcome to Modeling App!')).toBeVisible() | ||||
|       await expect(page.locator('.cm-content')).toContainText( | ||||
|         '// Shelf Bracket' | ||||
|       ) | ||||
|  | ||||
|       // There used to be old code here that checked if we stored the reset | ||||
|       // code into localStorage but that isnt the case on desktop. It gets | ||||
|       // saved to the file system, which we have other tests for. | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|     // Finish the onboarding | ||||
|     await expect(nextButton).toBeVisible() | ||||
|     await nextButton.click() | ||||
|  | ||||
|     // Test that the onboarding pane is gone | ||||
|     await expect(page.getByTestId('onboarding-content')).not.toBeVisible() | ||||
|     await expect(page.url()).not.toContain('onboarding') | ||||
|   }) | ||||
|  | ||||
|   test('Onboarding redirects and code updating', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Override beforeEach test setup | ||||
|     await page.addInitScript( | ||||
|       async ({ settingsKey, settings }) => { | ||||
|         // Give some initial code, so we can test that it's cleared | ||||
|         localStorage.setItem('persistCode', 'sigmaAllow = 15000') | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|   test( | ||||
|     'Click through each onboarding step', | ||||
|     { | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: 'incomplete', | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ settings: TEST_SETTINGS_ONBOARDING_EXPORT }), | ||||
|     }, | ||||
|     async ({ context, page, homePage }) => { | ||||
|       // Override beforeEach test setup | ||||
|       await context.addInitScript( | ||||
|         async ({ settingsKey, settings }) => { | ||||
|           // Give no initial code, so that the onboarding start is shown immediately | ||||
|           localStorage.setItem('persistCode', '') | ||||
|           localStorage.setItem(settingsKey, settings) | ||||
|         }, | ||||
|         { | ||||
|           settingsKey: TEST_SETTINGS_KEY, | ||||
|           settings: TOML.stringify({ | ||||
|             settings: TEST_SETTINGS_ONBOARDING_START, | ||||
|           }), | ||||
|         } | ||||
|       ) | ||||
|  | ||||
|       await page.setBodyDimensions({ width: 1200, height: 1080 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // Test that the onboarding pane loaded | ||||
|       await expect( | ||||
|         page.getByText('Welcome to Modeling App! This') | ||||
|       ).toBeVisible() | ||||
|  | ||||
|       const nextButton = page.getByTestId('onboarding-next') | ||||
|  | ||||
|       while ((await nextButton.innerText()) !== 'Finish') { | ||||
|         await nextButton.hover() | ||||
|         await nextButton.click() | ||||
|       } | ||||
|     ) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       // Finish the onboarding | ||||
|       await nextButton.hover() | ||||
|       await nextButton.click() | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // Test that the redirect happened | ||||
|     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 | ||||
|     await page.reload() | ||||
|     await expect(page.url().split(':3000').slice(-1)[0]).toBe( | ||||
|       `/file/%2Fbrowser%2Fmain.kcl/onboarding/export` | ||||
|     ) | ||||
|  | ||||
|     // Test that the onboarding pane loaded | ||||
|     const title = page.locator('[data-testid="onboarding-content"]') | ||||
|     await expect(title).toBeAttached() | ||||
|  | ||||
|     // 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 | ||||
|     await page.locator('[data-testid="onboarding-next"]').click() | ||||
|     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, | ||||
|       } | ||||
|     ) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 1080 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await page.waitForURL('**' + onboardingPaths.PARAMETRIC_MODELING, { | ||||
|       waitUntil: 'domcontentloaded', | ||||
|     }) | ||||
|  | ||||
|     const bracketNoNewLines = bracket.replace(/\n/g, '') | ||||
|  | ||||
|     // Check the code got reset on load | ||||
|     await expect(page.locator('#code-pane')).toBeVisible() | ||||
|     await expect(u.codeLocator).toHaveText(bracketNoNewLines, { | ||||
|       timeout: 10_000, | ||||
|     }) | ||||
|  | ||||
|     // Mess with the code again | ||||
|     await u.codeLocator.selectText() | ||||
|     await u.codeLocator.fill(badCode) | ||||
|     await expect(u.codeLocator).toHaveText(badCode) | ||||
|  | ||||
|     // Click to the next step | ||||
|     await page.locator('[data-testid="onboarding-next"]').click() | ||||
|     await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, { | ||||
|       waitUntil: 'domcontentloaded', | ||||
|     }) | ||||
|  | ||||
|     // Check that the code has been reset | ||||
|     await expect(u.codeLocator).toHaveText(bracketNoNewLines) | ||||
|   }) | ||||
|  | ||||
|   test('Avatar text updates depending on image load success', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     // Override beforeEach test setup | ||||
|     await page.addInitScript( | ||||
|       async ({ settingsKey, settings }) => { | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ | ||||
|           settings: TEST_SETTINGS_ONBOARDING_USER_MENU, | ||||
|         }), | ||||
|       } | ||||
|     ) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     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 | ||||
|     const avatarLocator = await page | ||||
|       .getByTestId('user-sidebar-toggle') | ||||
|       .locator('img') | ||||
|     const onboardingOverlayLocator = await page | ||||
|       .getByTestId('onboarding-content') | ||||
|       .locator('div') | ||||
|       .nth(1) | ||||
|  | ||||
|     // Expect the avatar to be visible and for the text to reference it | ||||
|     await expect(avatarLocator).toBeVisible() | ||||
|     await expect(onboardingOverlayLocator).toBeVisible() | ||||
|     await expect(onboardingOverlayLocator).toContainText('your avatar') | ||||
|  | ||||
|     // This is to force the avatar to 404. | ||||
|     // For our test image (only triggers locally. on CI, it's Kurt's / | ||||
|     // gravatar image ) | ||||
|     await page.route('/cat.jpg', async (route) => { | ||||
|       await route.fulfill({ | ||||
|         status: 404, | ||||
|         contentType: 'text/plain', | ||||
|         body: 'Not Found!', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     // 404 the CI avatar image | ||||
|     await page.route('https://lh3.googleusercontent.com/**', async (route) => { | ||||
|       await route.fulfill({ | ||||
|         status: 404, | ||||
|         contentType: 'text/plain', | ||||
|         body: 'Not Found!', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await page.reload({ waitUntil: 'domcontentloaded' }) | ||||
|  | ||||
|     // Now expect the text to be different | ||||
|     await expect(avatarLocator).not.toBeVisible() | ||||
|     await expect(onboardingOverlayLocator).toBeVisible() | ||||
|     await expect(onboardingOverlayLocator).toContainText('the menu button') | ||||
|   }) | ||||
|  | ||||
|   test("Avatar text doesn't mention avatar when no avatar", async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     // Override beforeEach test setup | ||||
|     await page.addInitScript( | ||||
|       async ({ settingsKey, settings }) => { | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|         localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE') | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ | ||||
|           settings: TEST_SETTINGS_ONBOARDING_USER_MENU, | ||||
|         }), | ||||
|       } | ||||
|     ) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     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 | ||||
|     const sidebar = page.getByTestId('user-sidebar-toggle') | ||||
|     const avatar = sidebar.locator('img') | ||||
|     const onboardingOverlayLocator = page | ||||
|       .getByTestId('onboarding-content') | ||||
|       .locator('div') | ||||
|       .nth(1) | ||||
|  | ||||
|     // Expect the avatar to be visible and for the text to reference it | ||||
|     await expect(avatar).not.toBeVisible() | ||||
|     await expect(onboardingOverlayLocator).toBeVisible() | ||||
|     await expect(onboardingOverlayLocator).toContainText('the menu button') | ||||
|  | ||||
|     // Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939 | ||||
|     // which doesn't deserver its own full test spun up | ||||
|     const userMenuFeatures = [ | ||||
|       'manage your account', | ||||
|       'report a bug', | ||||
|       'request a feature', | ||||
|       'sign out', | ||||
|     ] | ||||
|     for (const feature of userMenuFeatures) { | ||||
|       await expect(onboardingOverlayLocator).toContainText(feature) | ||||
|       // Test that the onboarding pane is gone | ||||
|       await expect(page.getByTestId('onboarding-content')).not.toBeVisible() | ||||
|       await expect.poll(() => page.url()).not.toContain('/onboarding') | ||||
|     } | ||||
|   }) | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     'Onboarding redirects and code updating', | ||||
|     { | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: '/export', | ||||
|         }, | ||||
|       }, | ||||
|       cleanProjectDir: true, | ||||
|     }, | ||||
|     async ({ context, page, homePage }) => { | ||||
|       const originalCode = 'sigmaAllow = 15000' | ||||
|  | ||||
|       // Override beforeEach test setup | ||||
|       await context.addInitScript( | ||||
|         async ({ settingsKey, settings }) => { | ||||
|           // Give some initial code, so we can test that it's cleared | ||||
|           localStorage.setItem('persistCode', originalCode) | ||||
|           localStorage.setItem(settingsKey, settings) | ||||
|         }, | ||||
|         { | ||||
|           settingsKey: TEST_SETTINGS_KEY, | ||||
|           settings: TOML.stringify({ | ||||
|             settings: TEST_SETTINGS_ONBOARDING_EXPORT, | ||||
|           }), | ||||
|         } | ||||
|       ) | ||||
|  | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // Test that the redirect happened | ||||
|       await expect.poll(() => page.url()).toContain('/onboarding/export') | ||||
|  | ||||
|       // Test that you come back to this page when you refresh | ||||
|       await page.reload() | ||||
|       await expect.poll(() => page.url()).toContain('/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 | ||||
|       const title = page.locator('[data-testid="onboarding-content"]') | ||||
|       await expect(title).toBeAttached() | ||||
|  | ||||
|       await expect(page.locator('.cm-content')).not.toHaveText(originalCode) | ||||
|  | ||||
|       // 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 expect(page.locator('.cm-content')).toHaveText(/.+/) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     'Onboarding code gets reset to demo on Interactive Numbers step', | ||||
|     { | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: '/parametric-modeling', | ||||
|         }, | ||||
|       }, | ||||
|       cleanProjectDir: true, | ||||
|     }, | ||||
|  | ||||
|     async ({ context, page, homePage }) => { | ||||
|       const u = await getUtils(page) | ||||
|       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, '') | ||||
|  | ||||
|       // Check the code got reset on load | ||||
|       await expect(page.locator('#code-pane')).toBeVisible() | ||||
|       await expect(u.codeLocator).toHaveText(bracketNoNewLines, { | ||||
|         timeout: 10_000, | ||||
|       }) | ||||
|  | ||||
|       // Mess with the code again | ||||
|       await u.codeLocator.selectText() | ||||
|       await u.codeLocator.fill(badCode) | ||||
|       await expect(u.codeLocator).toHaveText(badCode) | ||||
|  | ||||
|       // Click to the next step | ||||
|       await page.locator('[data-testid="onboarding-next"]').hover() | ||||
|       await page.locator('[data-testid="onboarding-next"]').click() | ||||
|       await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, { | ||||
|         waitUntil: 'domcontentloaded', | ||||
|       }) | ||||
|  | ||||
|       // Check that the code has been reset | ||||
|       await expect(u.codeLocator).toHaveText(bracketNoNewLines) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   // (lee) The two avatar tests are weird because even on main, we don't have | ||||
|   // anything to do with the avatar inside the onboarding test. Due to the | ||||
|   // 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 | ||||
|       await context.addInitScript( | ||||
|         async ({ settingsKey, settings }) => { | ||||
|           localStorage.setItem(settingsKey, settings) | ||||
|         }, | ||||
|         { | ||||
|           settingsKey: TEST_SETTINGS_KEY, | ||||
|           settings: TOML.stringify({ | ||||
|             settings: TEST_SETTINGS_ONBOARDING_USER_MENU, | ||||
|           }), | ||||
|         } | ||||
|       ) | ||||
|  | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // Test that the text in this step is correct | ||||
|       const avatarLocator = await page | ||||
|         .getByTestId('user-sidebar-toggle') | ||||
|         .locator('img') | ||||
|       const onboardingOverlayLocator = await page | ||||
|         .getByTestId('onboarding-content') | ||||
|         .locator('div') | ||||
|         .nth(1) | ||||
|  | ||||
|       // Expect the avatar to be visible and for the text to reference it | ||||
|       await expect(avatarLocator).toBeVisible() | ||||
|       await expect(onboardingOverlayLocator).toBeVisible() | ||||
|       await expect(onboardingOverlayLocator).toContainText('your avatar') | ||||
|  | ||||
|       // This is to force the avatar to 404. | ||||
|       // For our test image (only triggers locally. on CI, it's Kurt's / | ||||
|       // gravatar image ) | ||||
|       await page.route('/cat.jpg', async (route) => { | ||||
|         await route.fulfill({ | ||||
|           status: 404, | ||||
|           contentType: 'text/plain', | ||||
|           body: 'Not Found!', | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       // 404 the CI avatar image | ||||
|       await page.route( | ||||
|         'https://lh3.googleusercontent.com/**', | ||||
|         async (route) => { | ||||
|           await route.fulfill({ | ||||
|             status: 404, | ||||
|             contentType: 'text/plain', | ||||
|             body: 'Not Found!', | ||||
|           }) | ||||
|         } | ||||
|       ) | ||||
|  | ||||
|       await page.reload({ waitUntil: 'domcontentloaded' }) | ||||
|  | ||||
|       // Now expect the text to be different | ||||
|       await expect(avatarLocator).not.toBeVisible() | ||||
|       await expect(onboardingOverlayLocator).toBeVisible() | ||||
|       await expect(onboardingOverlayLocator).toContainText('the menu button') | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test.fixme( | ||||
|     "Avatar text doesn't mention avatar when no avatar", | ||||
|     { | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           onboardingStatus: 'incomplete', | ||||
|         }, | ||||
|       }, | ||||
|       cleanProjectDir: true, | ||||
|     }, | ||||
|     async ({ context, page, homePage }) => { | ||||
|       // Override beforeEach test setup | ||||
|       await context.addInitScript( | ||||
|         async ({ settingsKey, settings }) => { | ||||
|           localStorage.setItem(settingsKey, settings) | ||||
|           localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE') | ||||
|         }, | ||||
|         { | ||||
|           settingsKey: TEST_SETTINGS_KEY, | ||||
|           settings: TOML.stringify({ | ||||
|             settings: TEST_SETTINGS_ONBOARDING_USER_MENU, | ||||
|           }), | ||||
|         } | ||||
|       ) | ||||
|  | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // Test that the text in this step is correct | ||||
|       const sidebar = page.getByTestId('user-sidebar-toggle') | ||||
|       const avatar = sidebar.locator('img') | ||||
|       const onboardingOverlayLocator = page | ||||
|         .getByTestId('onboarding-content') | ||||
|         .locator('div') | ||||
|         .nth(1) | ||||
|  | ||||
|       // Expect the avatar to be visible and for the text to reference it | ||||
|       await expect(avatar).not.toBeVisible() | ||||
|       await expect(onboardingOverlayLocator).toBeVisible() | ||||
|       await expect(onboardingOverlayLocator).toContainText('the menu button') | ||||
|  | ||||
|       // Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939 | ||||
|       // which doesn't deserver its own full test spun up | ||||
|       const userMenuFeatures = [ | ||||
|         'manage your account', | ||||
|         'report a bug', | ||||
|         'request a feature', | ||||
|         'sign out', | ||||
|       ] | ||||
|       for (const feature of userMenuFeatures) { | ||||
|         await expect(onboardingOverlayLocator).toContainText(feature) | ||||
|       } | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| test( | ||||
|   'Restarting onboarding on desktop takes one attempt', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browser: _ }, testInfo) => { | ||||
|     const { electronApp, page } = await setupElectron({ | ||||
|       testInfo, | ||||
|       folderSetupFn: async (dir) => { | ||||
|         const routerTemplateDir = join(dir, 'router-template-slate') | ||||
|         await fsp.mkdir(routerTemplateDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('router-template-slate.kcl'), | ||||
|           join(routerTemplateDir, 'main.kcl') | ||||
|         ) | ||||
|   { | ||||
|     appSettings: { | ||||
|       app: { | ||||
|         onboardingStatus: 'dismissed', | ||||
|       }, | ||||
|     }, | ||||
|     cleanProjectDir: true, | ||||
|   }, | ||||
|   async ({ context, page, homePage }, testInfo) => { | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const routerTemplateDir = join(dir, 'router-template-slate') | ||||
|       await fsp.mkdir(routerTemplateDir, { recursive: true }) | ||||
|       await fsp.copyFile( | ||||
|         executorInputPath('router-template-slate.kcl'), | ||||
|         join(routerTemplateDir, 'main.kcl') | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     // Our constants | ||||
| @ -418,9 +443,8 @@ test( | ||||
|     const restartOnboardingButton = page.getByRole('button', { | ||||
|       name: 'Reset onboarding', | ||||
|     }) | ||||
|     const restartConfirmationButton = page.getByRole('button', { | ||||
|       name: 'Make a new project', | ||||
|     }) | ||||
|     const nextButton = page.getByTestId('onboarding-next') | ||||
|  | ||||
|     const tutorialProjectIndicator = page | ||||
|       .getByTestId('project-sidebar-toggle') | ||||
|       .filter({ hasText: 'Tutorial Project 00' }) | ||||
| @ -439,7 +463,7 @@ test( | ||||
|     }) | ||||
|  | ||||
|     await test.step('Navigate into project', async () => { | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       page.on('console', console.log) | ||||
|  | ||||
| @ -455,8 +479,8 @@ test( | ||||
|       await helpMenuButton.click() | ||||
|       await restartOnboardingButton.click() | ||||
|  | ||||
|       await expect(restartConfirmationButton).toBeVisible() | ||||
|       await restartConfirmationButton.click() | ||||
|       await nextButton.hover() | ||||
|       await nextButton.click() | ||||
|     }) | ||||
|  | ||||
|     await test.step('Confirm that the onboarding has restarted', async () => { | ||||
| @ -480,11 +504,9 @@ test( | ||||
|  | ||||
|       await restartOnboardingSettingsButton.click() | ||||
|       // Since the code is empty, we should not see the confirmation dialog | ||||
|       await expect(restartConfirmationButton).not.toBeVisible() | ||||
|       await expect(nextButton).not.toBeVisible() | ||||
|       await expect(tutorialProjectIndicator).toBeVisible() | ||||
|       await expect(tutorialModalText).toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     await electronApp.close() | ||||
|   } | ||||
| ) | ||||
|  | ||||
| @ -1,25 +1,49 @@ | ||||
| import { test, expect, AuthenticatedApp } from './fixtures/fixtureSetup' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
| import { EditorFixture } from './fixtures/editorFixture' | ||||
| import { SceneFixture } from './fixtures/sceneFixture' | ||||
| 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( | ||||
|   'verify extruding circle works', | ||||
|   { tag: ['@skipWin'] }, | ||||
|   async ({ app, cmdBar, 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('test-circle-extrude.kcl') | ||||
|     await app.initialise(file) | ||||
|     const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217) | ||||
| test('verify extruding circle works', async ({ | ||||
|   context, | ||||
|   homePage, | ||||
|   cmdBar, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   scene, | ||||
| }) => { | ||||
|   const file = await fs.readFile( | ||||
|     path.resolve( | ||||
|       __dirname, | ||||
|       '../../', | ||||
|       './src/wasm-lib/tests/executor/inputs/test-circle-extrude.kcl' | ||||
|     ), | ||||
|     'utf-8' | ||||
|   ) | ||||
|   await context.addInitScript((file) => { | ||||
|     localStorage.setItem('persistCode', file) | ||||
|   }, file) | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|     await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => { | ||||
|       await scene.clickNoWhere() | ||||
|       await expect(toolbar.extrudeButton).toBeEnabled() | ||||
|   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 scene.clickNoWhere() | ||||
|     await expect(toolbar.extrudeButton).toBeEnabled() | ||||
|   }) | ||||
|  | ||||
|   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, | ||||
|       diagnostics: [], | ||||
|     }) | ||||
|  | ||||
|     await test.step('check code model connection works and that button is still enable once circle is selected ', async () => { | ||||
| @ -27,7 +51,7 @@ test( | ||||
|       const circleSnippet = | ||||
|         'circle({ center = [318.33, 168.1], radius = 182.8 }, %)' | ||||
|       await editor.expectState({ | ||||
|         activeLines: [], | ||||
|         activeLines: ["constsketch002=startSketchOn('XZ')"], | ||||
|         highlightedCode: circleSnippet, | ||||
|         diagnostics: [], | ||||
|       }) | ||||
| @ -40,39 +64,40 @@ test( | ||||
|       }) | ||||
|       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 toolbar.extrudeButton.click() | ||||
|   await test.step('do extrude flow and check extrude code is added to editor', async () => { | ||||
|     await toolbar.extrudeButton.click() | ||||
|  | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'arguments', | ||||
|         currentArgKey: 'distance', | ||||
|         currentArgValue: '5', | ||||
|         headerArguments: { Selection: '1 face', Distance: '' }, | ||||
|         highlightedHeaderArg: 'distance', | ||||
|         commandName: 'Extrude', | ||||
|       }) | ||||
|       await cmdBar.progressCmdBar() | ||||
|  | ||||
|       const expectString = 'extrude001 = extrude(5, sketch001)' | ||||
|       await editor.expectEditor.not.toContain(expectString) | ||||
|  | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'review', | ||||
|         headerArguments: { Selection: '1 face', Distance: '5' }, | ||||
|         commandName: 'Extrude', | ||||
|       }) | ||||
|       await cmdBar.progressCmdBar() | ||||
|  | ||||
|       await editor.expectEditor.toContain(expectString) | ||||
|     await cmdBar.expectState({ | ||||
|       stage: 'arguments', | ||||
|       currentArgKey: 'distance', | ||||
|       currentArgValue: '5', | ||||
|       headerArguments: { Selection: '1 face', Distance: '' }, | ||||
|       highlightedHeaderArg: 'distance', | ||||
|       commandName: 'Extrude', | ||||
|     }) | ||||
|   } | ||||
| ) | ||||
|     await cmdBar.progressCmdBar() | ||||
|  | ||||
|     const expectString = 'extrude001 = extrude(5, sketch001)' | ||||
|     await editor.expectEditor.not.toContain(expectString) | ||||
|  | ||||
|     await cmdBar.expectState({ | ||||
|       stage: 'review', | ||||
|       headerArguments: { Selection: '1 face', Distance: '5' }, | ||||
|       commandName: 'Extrude', | ||||
|     }) | ||||
|     await cmdBar.progressCmdBar() | ||||
|  | ||||
|     await editor.expectEditor.toContain(expectString) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test.describe('verify sketch on chamfer works', () => { | ||||
|   const _sketchOnAChamfer = | ||||
|     ( | ||||
|       app: AuthenticatedApp, | ||||
|       page: Page, | ||||
|       editor: EditorFixture, | ||||
|       toolbar: ToolbarFixture, | ||||
|       scene: SceneFixture | ||||
| @ -124,7 +149,7 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|         await toolbar.startSketchPlaneSelection() | ||||
|         await clickChamfer() | ||||
|         // timeout wait for engine animation is unavoidable | ||||
|         await app.page.waitForTimeout(600) | ||||
|         await page.waitForTimeout(1000) | ||||
|         await editor.expectEditor.toContain(afterChamferSelectSnippet) | ||||
|       }) | ||||
|       await test.step('make sure a basic sketch can be added', async () => { | ||||
| @ -135,7 +160,9 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|           pixelDiff: 50, | ||||
|         }) | ||||
|         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 () => { | ||||
| @ -150,24 +177,35 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
|   test( | ||||
|     'works on all edge selections and 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.kcl') | ||||
|       await app.initialise(file) | ||||
|   test('works on all edge selections and 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.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(app, editor, toolbar, scene) | ||||
|     const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene) | ||||
|  | ||||
|       await sketchOnAChamfer({ | ||||
|         clickCoords: { x: 570, y: 220 }, | ||||
|         cameraPos: { x: 16020, y: -2000, z: 10500 }, | ||||
|         cameraTarget: { x: -150, y: -4500, z: -80 }, | ||||
|         beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01) | ||||
|     await sketchOnAChamfer({ | ||||
|       clickCoords: { x: 570, y: 220 }, | ||||
|       cameraPos: { x: 16020, y: -2000, z: 10500 }, | ||||
|       cameraTarget: { x: -150, y: -4500, z: -80 }, | ||||
|       beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01) | ||||
|       chamfer({length = 30,tags = [ | ||||
|       seg01, | ||||
|       getNextAdjacentEdge(yo), | ||||
| @ -175,10 +213,9 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|       getOppositeEdge(seg01) | ||||
|     ]}, %)`, | ||||
|  | ||||
|         afterChamferSelectSnippet: | ||||
|           'sketch002 = startSketchOn(extrude001, seg03)', | ||||
|         afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', | ||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||
|       afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)', | ||||
|       afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', | ||||
|       afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||
|     |> angledLine([ | ||||
|          segAng(rectangleSegmentA002) - 90, | ||||
|          105.26 | ||||
| @ -189,13 +226,13 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|        ], %, $rectangleSegmentC001) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)`, | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|       await sketchOnAChamfer({ | ||||
|         clickCoords: { x: 690, y: 250 }, | ||||
|         cameraPos: { x: 16020, y: -2000, z: 10500 }, | ||||
|         cameraTarget: { x: -150, y: -4500, z: -80 }, | ||||
|         beforeChamferSnippet: `angledLine([ | ||||
|     await sketchOnAChamfer({ | ||||
|       clickCoords: { x: 690, y: 250 }, | ||||
|       cameraPos: { x: 16020, y: -2000, z: 10500 }, | ||||
|       cameraTarget: { x: -150, y: -4500, z: -80 }, | ||||
|       beforeChamferSnippet: `angledLine([ | ||||
|          segAng(rectangleSegmentA001) - 90, | ||||
|          217.26 | ||||
|        ], %, $seg01)chamfer({ | ||||
| @ -207,10 +244,9 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|          ] | ||||
|        }, %)`, | ||||
|  | ||||
|         afterChamferSelectSnippet: | ||||
|           'sketch003 = startSketchOn(extrude001, seg04)', | ||||
|         afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', | ||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||
|       afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)', | ||||
|       afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', | ||||
|       afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||
|     |> angledLine([ | ||||
|          segAng(rectangleSegmentA003) - 90, | ||||
|          106.84 | ||||
| @ -221,22 +257,21 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|        ], %, $rectangleSegmentC002) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)`, | ||||
|       }) | ||||
|       await sketchOnAChamfer({ | ||||
|         clickCoords: { x: 677, y: 87 }, | ||||
|         cameraPos: { x: -6200, y: 1500, z: 6200 }, | ||||
|         cameraTarget: { x: 8300, y: 1100, z: 4800 }, | ||||
|         beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({ | ||||
|     }) | ||||
|     await sketchOnAChamfer({ | ||||
|       clickCoords: { x: 677, y: 87 }, | ||||
|       cameraPos: { x: -6200, y: 1500, z: 6200 }, | ||||
|       cameraTarget: { x: 8300, y: 1100, z: 4800 }, | ||||
|       beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({ | ||||
|          length = 30, | ||||
|          tags = [ | ||||
|            getNextAdjacentEdge(yo), | ||||
|            getNextAdjacentEdge(seg02) | ||||
|          ] | ||||
|        }, %)`, | ||||
|         afterChamferSelectSnippet: | ||||
|           'sketch003 = startSketchOn(extrude001, seg04)', | ||||
|         afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', | ||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||
|       afterChamferSelectSnippet: 'sketch003 = startSketchOn(extrude001, seg04)', | ||||
|       afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)', | ||||
|       afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||
|     |> angledLine([ | ||||
|          segAng(rectangleSegmentA003) - 90, | ||||
|          106.84 | ||||
| @ -247,20 +282,19 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|        ], %, $rectangleSegmentC002) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)`, | ||||
|       }) | ||||
|       /// last one | ||||
|       await sketchOnAChamfer({ | ||||
|         clickCoords: { x: 620, y: 300 }, | ||||
|         cameraPos: { x: -1100, y: -7700, z: 1600 }, | ||||
|         cameraTarget: { x: 1450, y: 670, z: 4000 }, | ||||
|         beforeChamferSnippet: `chamfer({ | ||||
|     }) | ||||
|     /// last one | ||||
|     await sketchOnAChamfer({ | ||||
|       clickCoords: { x: 620, y: 300 }, | ||||
|       cameraPos: { x: -1100, y: -7700, z: 1600 }, | ||||
|       cameraTarget: { x: 1450, y: 670, z: 4000 }, | ||||
|       beforeChamferSnippet: `chamfer({ | ||||
|          length = 30, | ||||
|          tags = [getNextAdjacentEdge(yo)] | ||||
|        }, %)`, | ||||
|         afterChamferSelectSnippet: | ||||
|           'sketch005 = startSketchOn(extrude001, seg06)', | ||||
|         afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)', | ||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005) | ||||
|       afterChamferSelectSnippet: 'sketch005 = startSketchOn(extrude001, seg06)', | ||||
|       afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)', | ||||
|       afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005) | ||||
|  | ||||
|     |> angledLine([ | ||||
|          segAng(rectangleSegmentA005) - 90, | ||||
| @ -272,11 +306,11 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|        ], %, $rectangleSegmentC004) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)`, | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|       await test.step('verify at the end of the test that final code is what is expected', async () => { | ||||
|         await editor.expectEditor.toContain( | ||||
|           `sketch001 = startSketchOn('XZ') | ||||
|     await test.step('verify at the end of the test that final code is what is expected', async () => { | ||||
|       await editor.expectEditor.toContain( | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|  | ||||
|       |> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag] | ||||
|       |> angledLine([0, 268.43], %, $rectangleSegmentA001) | ||||
| @ -305,7 +339,7 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|            tags = [getNextAdjacentEdge(yo)] | ||||
|          }, %, $seg06) | ||||
|     sketch005 = startSketchOn(extrude001, seg06) | ||||
|       |> startProfileAt([-23.43, 19.69], %) | ||||
|       |> startProfileAt([-23.43,19.69], %) | ||||
|       |> angledLine([0, 9.1], %, $rectangleSegmentA005) | ||||
|       |> angledLine([ | ||||
|            segAng(rectangleSegmentA005) - 90, | ||||
| @ -318,7 +352,7 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|       |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|       |> close(%) | ||||
|     sketch004 = startSketchOn(extrude001, seg05) | ||||
|       |> startProfileAt([82.57, 322.96], %) | ||||
|       |> startProfileAt([82.57,322.96], %) | ||||
|       |> angledLine([0, 11.16], %, $rectangleSegmentA004) | ||||
|       |> angledLine([ | ||||
|            segAng(rectangleSegmentA004) - 90, | ||||
| @ -331,7 +365,7 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|       |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|       |> close(%) | ||||
|     sketch003 = startSketchOn(extrude001, seg04) | ||||
|       |> startProfileAt([-209.64, 255.28], %) | ||||
|       |> startProfileAt([-209.64,255.28], %) | ||||
|       |> angledLine([0, 11.56], %, $rectangleSegmentA003) | ||||
|       |> angledLine([ | ||||
|            segAng(rectangleSegmentA003) - 90, | ||||
| @ -344,7 +378,7 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|       |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|       |> close(%) | ||||
|     sketch002 = startSketchOn(extrude001, seg03) | ||||
|       |> startProfileAt([205.96, 254.59], %) | ||||
|       |> startProfileAt([205.96,254.59], %) | ||||
|       |> angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||
|       |> angledLine([ | ||||
|            segAng(rectangleSegmentA002) - 90, | ||||
| @ -357,43 +391,50 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|       |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|       |> close(%) | ||||
|     `, | ||||
|           { shouldNormalise: true } | ||||
|         ) | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   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.' | ||||
|         { shouldNormalise: true } | ||||
|       ) | ||||
|       const file = await app.getInputFile( | ||||
|         'e2e-can-sketch-on-chamfer-no-pipeExpr.kcl' | ||||
|       ) | ||||
|       await app.initialise(file) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|       const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene) | ||||
|   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() | ||||
|  | ||||
|       await sketchOnAChamfer({ | ||||
|         clickCoords: { x: 570, y: 220 }, | ||||
|         cameraPos: { x: 16020, y: -2000, z: 10500 }, | ||||
|         cameraTarget: { x: -150, y: -4500, z: -80 }, | ||||
|         beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01) | ||||
|     const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene) | ||||
|  | ||||
|     await sketchOnAChamfer({ | ||||
|       clickCoords: { x: 570, y: 220 }, | ||||
|       cameraPos: { x: 16020, y: -2000, z: 10500 }, | ||||
|       cameraTarget: { x: -150, y: -4500, z: -80 }, | ||||
|       beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01) | ||||
|       chamfer({length=30,tags=[ | ||||
|       seg01, | ||||
|       getNextAdjacentEdge(yo), | ||||
|       getNextAdjacentEdge(seg02), | ||||
|       getOppositeEdge(seg01) | ||||
|     ]}, extrude001)`, | ||||
|         beforeChamferSnippetEnd: '}, extrude001)', | ||||
|         afterChamferSelectSnippet: | ||||
|           'sketch002 = startSketchOn(extrude001, seg03)', | ||||
|         afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', | ||||
|         afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||
|       beforeChamferSnippetEnd: '}, extrude001)', | ||||
|       afterChamferSelectSnippet: 'sketch002 = startSketchOn(extrude001, seg03)', | ||||
|       afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', | ||||
|       afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) | ||||
|     |> angledLine([ | ||||
|          segAng(rectangleSegmentA002) - 90, | ||||
|          105.26 | ||||
| @ -404,9 +445,9 @@ test.describe('verify sketch on chamfer works', () => { | ||||
|        ], %, $rectangleSegmentC001) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)`, | ||||
|       }) | ||||
|       await editor.expectEditor.toContain( | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|     }) | ||||
|     await editor.expectEditor.toContain( | ||||
|       `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([75.8, 317.2], %) | ||||
|   |> angledLine([0, 268.43], %, $rectangleSegmentA001) | ||||
|   |> angledLine([ | ||||
| @ -446,50 +487,56 @@ sketch002 = startSketchOn(extrude001, seg03) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| `, | ||||
|         { shouldNormalise: true } | ||||
|       ) | ||||
|     } | ||||
|   ) | ||||
|       { shouldNormalise: true } | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Verify axis, origin, and horizontal snapping`, async ({ | ||||
|   app, | ||||
|   page, | ||||
|   homePage, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   scene, | ||||
| }) => { | ||||
|   const viewPortSize = { width: 1200, height: 500 } | ||||
|  | ||||
|   await page.setBodyDimensions(viewPortSize) | ||||
|  | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   // Constants and locators | ||||
|   // These are mappings from screenspace to KCL coordinates, | ||||
|   // until we merge in our coordinate system helpers | ||||
|   const xzPlane = [ | ||||
|     app.viewPortSize.width * 0.65, | ||||
|     app.viewPortSize.height * 0.3, | ||||
|     viewPortSize.width * 0.65, | ||||
|     viewPortSize.height * 0.3, | ||||
|   ] as const | ||||
|   const originSloppy = { | ||||
|     screen: [ | ||||
|       app.viewPortSize.width / 2 + 3, // 3px off the center of the screen | ||||
|       app.viewPortSize.height / 2, | ||||
|       viewPortSize.width / 2 + 3, // 3px off the center of the screen | ||||
|       viewPortSize.height / 2, | ||||
|     ], | ||||
|     kcl: [0, 0], | ||||
|   } as const | ||||
|   const xAxisSloppy = { | ||||
|     screen: [ | ||||
|       app.viewPortSize.width * 0.75, | ||||
|       app.viewPortSize.height / 2 - 3, // 3px off the X-axis | ||||
|       viewPortSize.width * 0.75, | ||||
|       viewPortSize.height / 2 - 3, // 3px off the X-axis | ||||
|     ], | ||||
|     kcl: [16.95, 0], | ||||
|     kcl: [20.34, 0], | ||||
|   } as const | ||||
|   const offYAxis = { | ||||
|     screen: [ | ||||
|       app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range | ||||
|       app.viewPortSize.height * 0.3, | ||||
|       viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range | ||||
|       viewPortSize.height * 0.3, | ||||
|     ], | ||||
|     kcl: [6.78, 6.78], | ||||
|     kcl: [8.14, 6.78], | ||||
|   } as const | ||||
|   const yAxisSloppy = { | ||||
|     screen: [ | ||||
|       app.viewPortSize.width / 2 + 5, // 5px off the Y-axis | ||||
|       app.viewPortSize.height * 0.3, | ||||
|       viewPortSize.width / 2 + 5, // 5px off the Y-axis | ||||
|       viewPortSize.height * 0.3, | ||||
|     ], | ||||
|     kcl: [0, 6.78], | ||||
|   } as const | ||||
| @ -510,15 +557,13 @@ test(`Verify axis, origin, and horizontal snapping`, async ({ | ||||
|     afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`, | ||||
|   } | ||||
|  | ||||
|   await app.initialise() | ||||
|  | ||||
|   await test.step(`Start a sketch on the XZ plane`, async () => { | ||||
|     await editor.closePane() | ||||
|     await toolbar.startSketchPlaneSelection() | ||||
|     await moveToXzPlane() | ||||
|     await clickOnXzPlane() | ||||
|     // timeout wait for engine animation is unavoidable | ||||
|     await app.page.waitForTimeout(600) | ||||
|     await page.waitForTimeout(600) | ||||
|     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 () => { | ||||
| @ -553,11 +598,15 @@ test(`Verify axis, origin, and horizontal snapping`, async ({ | ||||
| }) | ||||
|  | ||||
| test(`Verify user can double-click to edit a sketch`, async ({ | ||||
|   app, | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   scene, | ||||
| }) => { | ||||
|   const u = await getUtils(page) | ||||
|  | ||||
|   const initialCode = `closedSketch = startSketchOn('XZ') | ||||
|   |> circle({ center = [8, 5], radius = 2 }, %) | ||||
| openSketch = startSketchOn('XY') | ||||
| @ -566,15 +615,24 @@ openSketch = startSketchOn('XY') | ||||
|   |> xLine(5, %) | ||||
|   |> tangentialArcTo([10, 0], %) | ||||
| ` | ||||
|   await app.initialise(initialCode) | ||||
|   const viewPortSize = { width: 1000, height: 500 } | ||||
|   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 = { | ||||
|     x: app.viewPortSize.width * 0.63, | ||||
|     y: app.viewPortSize.height * 0.5, | ||||
|     x: viewPortSize.width * 0.63, | ||||
|     y: viewPortSize.height * 0.5, | ||||
|   } | ||||
|   const pointOnPathAfterSketching = { | ||||
|     x: app.viewPortSize.width * 0.58, | ||||
|     y: app.viewPortSize.height * 0.5, | ||||
|     x: viewPortSize.width * 0.65, | ||||
|     y: viewPortSize.height * 0.5, | ||||
|   } | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] = | ||||
| @ -607,41 +665,59 @@ openSketch = startSketchOn('XY') | ||||
|       diagnostics: [], | ||||
|     }) | ||||
|   }) | ||||
|   await page.waitForTimeout(1000) | ||||
|  | ||||
|   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 moveToOpenPath() | ||||
|     await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15) | ||||
|     // There is a full execution after exiting sketch that clears the scene. | ||||
|     await app.page.waitForTimeout(500) | ||||
|     await page.waitForTimeout(500) | ||||
|     await dblClickOpenPath() | ||||
|     await expect(toolbar.startSketchBtn).not.toBeVisible() | ||||
|     await expect(toolbar.exitSketchBtn).toBeVisible() | ||||
|     // Wait for enter sketch mode to complete | ||||
|     await app.page.waitForTimeout(500) | ||||
|     await page.waitForTimeout(500) | ||||
|     await editor.expectState({ | ||||
|       activeLines: [`|>xLine(5,%)`], | ||||
|       highlightedCode: 'xLine(5,%)', | ||||
|       activeLines: [`|>tangentialArcTo([10,0],%)`], | ||||
|       highlightedCode: 'tangentialArcTo([10,0],%)', | ||||
|       diagnostics: [], | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test(`Offset plane point-and-click`, async ({ | ||||
|   app, | ||||
|   context, | ||||
|   page, | ||||
|   homePage, | ||||
|   scene, | ||||
|   editor, | ||||
|   toolbar, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   await app.initialise() | ||||
|  | ||||
|   // One dumb hardcoded screen pixel value | ||||
|   const testPoint = { x: 700, y: 150 } | ||||
|   const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|   const expectedOutput = `plane001 = offsetPlane('XZ', 5)` | ||||
|  | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   await test.step(`Look for the blue of the XZ plane`, async () => { | ||||
|     await scene.expectPixelColor([50, 51, 96], testPoint, 15) | ||||
|   }) | ||||
| @ -684,20 +760,30 @@ const loftPointAndClickCases = [ | ||||
| ] | ||||
| loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||
|   test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({ | ||||
|     app, | ||||
|     context, | ||||
|     homePage, | ||||
|     page, | ||||
|     scene, | ||||
|     editor, | ||||
|     toolbar, | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     const initialCode = `sketch001 = startSketchOn('XZ') | ||||
|     |> circle({ center = [0, 0], radius = 30 }, %) | ||||
|     plane001 = offsetPlane('XZ', 50) | ||||
|     sketch002 = startSketchOn(plane001) | ||||
|     |> circle({ center = [0, 0], radius = 20 }, %) | ||||
| ` | ||||
|     await app.initialise(initialCode) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await context.addInitScript((code) => { | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, initialCode) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     // One dumb hardcoded screen pixel value | ||||
|     const testPoint = { x: 575, y: 200 } | ||||
| @ -716,7 +802,7 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => { | ||||
|       await clickOnSketch1() | ||||
|       await page.keyboard.down('Shift') | ||||
|       await clickOnSketch2() | ||||
|       await app.page.waitForTimeout(500) | ||||
|       await page.waitForTimeout(500) | ||||
|       await page.keyboard.up('Shift') | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -1,46 +1,37 @@ | ||||
| import { test, expect, Page } from '@playwright/test' | ||||
| import { join } from 'path' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
| import path from 'path' | ||||
| import * as fsp from 'fs/promises' | ||||
| import { | ||||
|   getUtils, | ||||
|   setup, | ||||
|   setupElectron, | ||||
|   tearDown, | ||||
|   executorInputPath, | ||||
| } from './test-utils' | ||||
| import { getUtils, executorInputPath } from './test-utils' | ||||
| import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates' | ||||
| 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', () => { | ||||
|   // bugs we found that don't fit neatly into other categories | ||||
|   test('bad model has inline error #3251', async ({ page }) => { | ||||
|   test('bad model has inline error #3251', async ({ | ||||
|     context, | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // because the model has `line([0,0]..` it is valid code, but the model is invalid | ||||
|     // regression test for https://github.com/KittyCAD/modeling-app/issues/3251 | ||||
|     // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|     await context.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch2 = startSketchOn("XY") | ||||
| sketch001 = startSketchAt([-0, -0]) | ||||
|   |> line([0, 0], %) | ||||
|   |> line([-4.84, -5.29], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%)` | ||||
|   sketch001 = startSketchAt([-0, -0]) | ||||
|     |> line([0, 0], %) | ||||
|     |> line([-4.84, -5.29], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     // error in guter | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
| @ -56,6 +47,7 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|   }) | ||||
|   test('user should not have to press down twice in cmdbar', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // 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 | ||||
| @ -64,26 +56,38 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch2 = startSketchOn("XY") | ||||
| sketch001 = startSketchAt([-0, -0]) | ||||
|   |> line([0, 0], %) | ||||
|   |> line([-4.84, -5.29], %) | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([82.33, 238.21], %) | ||||
|   |> angledLine([0, 288.63], %, $rectangleSegmentA001) | ||||
|   |> angledLine([ | ||||
|        segAng(rectangleSegmentA001) - 90, | ||||
|        197.97 | ||||
|      ], %, $rectangleSegmentB001) | ||||
|   |> angledLine([ | ||||
|        segAng(rectangleSegmentA001), | ||||
|        -segLen(rectangleSegmentA001) | ||||
|      ], %, $rectangleSegmentC001) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%)` | ||||
|   |> close(%) | ||||
| extrude001 = extrude(50, sketch001) | ||||
| ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await page.goto('/') | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     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 | ||||
|         .getByRole('option', { name: 'floppy disk arrow Export' }) | ||||
|         .click() | ||||
|       const floppy = page.getByRole('option', { | ||||
|         name: 'floppy disk arrow Export', | ||||
|       }) | ||||
|  | ||||
|       await floppy.click() | ||||
|  | ||||
|       // press arrow down key twice | ||||
|       await page.keyboard.press('ArrowDown') | ||||
| @ -115,21 +119,22 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|       ) | ||||
|     }) | ||||
|   }) | ||||
|   test('executes on load', async ({ page }) => { | ||||
|   test('executes on load', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('-XZ') | ||||
|     |> startProfileAt([-6.95, 4.98], %) | ||||
|     |> line([25.1, 0.41], %) | ||||
|     |> line([0.73, -14.93], %) | ||||
|     |> line([-23.44, 0.52], %)` | ||||
|   |> startProfileAt([-6.95, 4.98], %) | ||||
|   |> line([25.1, 0.41], %) | ||||
|   |> line([0.73, -14.93], %) | ||||
|   |> line([-23.44, 0.52], %)` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     // expand variables section | ||||
|     const variablesTabButton = page.getByTestId('variables-pane-button') | ||||
| @ -148,14 +153,15 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('re-executes', async ({ page }) => { | ||||
|   test('re-executes', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem('persistCode', `myVar = 5`) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     const variablesTabButton = page.getByTestId('variables-pane-button') | ||||
|     await variablesTabButton.click() | ||||
| @ -174,32 +180,33 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|       page.locator('.pretty-json-container >> text=myVar:67') | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
|   test('ProgramMemory can be serialised', async ({ page }) => { | ||||
|   test('ProgramMemory can be serialised', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `part = startSketchOn('XY') | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([0, 1], %) | ||||
|     |> line([1, 0], %) | ||||
|     |> line([0, -1], %) | ||||
|     |> close(%) | ||||
|     |> extrude(1, %) | ||||
|     |> patternLinear3d({ | ||||
|           axis: [1, 0, 1], | ||||
|           repetitions: 3, | ||||
|           distance: 6 | ||||
|         }, %)` | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([0, 1], %) | ||||
|   |> line([1, 0], %) | ||||
|   |> line([0, -1], %) | ||||
|   |> close(%) | ||||
|   |> extrude(1, %) | ||||
|   |> patternLinear3d({ | ||||
|         axis: [1, 0, 1], | ||||
|         repetitions: 3, | ||||
|         distance: 6 | ||||
|       }, %)` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     const messages: string[] = [] | ||||
|  | ||||
|     // Listen for all console events and push the message text to an array | ||||
|     page.on('console', (message) => messages.push(message.text())) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -212,19 +219,26 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
|   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) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     const zooLogo = page.locator('[data-testid="app-logo"]') | ||||
|     // Make sure it's not a link | ||||
|     await expect(zooLogo).not.toHaveAttribute('href') | ||||
|   }) | ||||
|  | ||||
|   test( | ||||
|     'Position _ Is Out Of Range... regression test', | ||||
|     { tag: ['@skipWin'] }, | ||||
|     async ({ page }) => { | ||||
|     async ({ context, page, homePage }) => { | ||||
|       // SKip on windows, its being weird. | ||||
|       test.skip( | ||||
|         process.platform === 'win32', | ||||
| @ -233,25 +247,26 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|  | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.addInitScript(async () => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await context.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `exampleSketch = startSketchOn("XZ") | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> angledLine({ angle: 50, length: 45 }, %) | ||||
|     |> yLineTo(0, %) | ||||
|     |> close(%) | ||||
|     |> | ||||
|  | ||||
|   example = extrude(5, exampleSketch) | ||||
|   shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)` | ||||
|       |> startProfileAt([0, 0], %) | ||||
|       |> angledLine({ angle: 50, length: 45 }, %) | ||||
|       |> yLineTo(0, %) | ||||
|       |> close(%) | ||||
|       |> | ||||
|    | ||||
|     example = extrude(5, exampleSketch) | ||||
|     shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)` | ||||
|         ) | ||||
|       }) | ||||
|  | ||||
|       await expect(async () => { | ||||
|         await page.goto('/') | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         // error in guter | ||||
|         await expect(page.locator('.cm-lint-marker-error')).toBeVisible({ | ||||
|           timeout: 1_000, | ||||
| @ -293,12 +308,12 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|  | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toContainText(`exampleSketch = startSketchOn("XZ") | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> angledLine({ angle: 50, length: 45 }, %) | ||||
|     |> yLineTo(0, %) | ||||
|     |> close(%) | ||||
|  | ||||
|     thing: "blah"`) | ||||
|       |> startProfileAt([0, 0], %) | ||||
|       |> angledLine({ angle: 50, length: 45 }, %) | ||||
|       |> yLineTo(0, %) | ||||
|       |> close(%) | ||||
|    | ||||
|       thing: "blah"`) | ||||
|  | ||||
|       await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|     } | ||||
| @ -306,6 +321,7 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|  | ||||
|   test('when engine fails export we handle the failure and alert the user', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript( | ||||
| @ -316,9 +332,10 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|       { code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } | ||||
|     ) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -374,7 +391,6 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
|     await u.clearCommandLogs() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
| @ -408,7 +424,7 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|   test( | ||||
|     'ensure you can not export while an export is already going', | ||||
|     { tag: ['@skipLinux', '@skipWin'] }, | ||||
|     async ({ page }) => { | ||||
|     async ({ page, homePage }) => { | ||||
|       // This is being weird on ubuntu and windows. | ||||
|       test.skip( | ||||
|         // eslint-disable-next-line jest/valid-title | ||||
| @ -428,9 +444,10 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|           } | ||||
|         ) | ||||
|  | ||||
|         await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         // wait for execution done | ||||
|         await u.openDebugPanel() | ||||
| @ -500,20 +517,17 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|   test( | ||||
|     `Network health indicator only appears in modeling view`, | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName: _ }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async (dir) => { | ||||
|           const bracketDir = join(dir, 'bracket') | ||||
|           await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|           await fsp.copyFile( | ||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|             join(bracketDir, 'main.kcl') | ||||
|           ) | ||||
|         }, | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       await context.folderSetupFn(async (dir) => { | ||||
|         const bracketDir = path.join(dir, 'bracket') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|           path.join(bracketDir, 'main.kcl') | ||||
|         ) | ||||
|       }) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
|       // Locators | ||||
| @ -539,13 +553,12 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|         await u.waitForPageLoad() | ||||
|         await expect(networkHealthIndicator).toContainText('Connected') | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test(`View gizmo stays visible even when zoomed out all the way`, async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
| @ -561,8 +574,9 @@ sketch001 = startSketchAt([-0, -0]) | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem('persistCode', '') | ||||
|       }) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.waitForPageLoad() | ||||
|       await u.closeKclCodePanel() | ||||
|     }) | ||||
|  | ||||
|  | ||||
| @ -1,27 +1,20 @@ | ||||
| import { test, expect, Page } from '@playwright/test' | ||||
| import { test as test2, expect as expect2 } from './fixtures/fixtureSetup' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
| import fs from 'node:fs/promises' | ||||
| import path from 'node:path' | ||||
| import { HomePageFixture } from './fixtures/homePageFixture' | ||||
|  | ||||
| import { | ||||
|   getMovementUtils, | ||||
|   getUtils, | ||||
|   PERSIST_MODELING_CONTEXT, | ||||
|   setup, | ||||
|   tearDown, | ||||
| } from './test-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('multi-sketch file shows multiple Edit Sketch buttons', async ({ | ||||
|     page, | ||||
|     context, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     const selectionsSnippets = { | ||||
| @ -35,53 +28,53 @@ test.describe('Sketch tests', () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           ` | ||||
|   width = 20 | ||||
|   height = 10 | ||||
|   thickness = 5 | ||||
|   screwRadius = 3 | ||||
|   wireRadius = 2 | ||||
|   wireOffset = 0.5 | ||||
|  | ||||
|   screwHole = startSketchOn('XY') | ||||
|     ${startProfileAt1} | ||||
|     |> arc({ | ||||
|           radius = screwRadius, | ||||
|           angle_start = 0, | ||||
|           angle_end = 360 | ||||
|         }, %) | ||||
|  | ||||
|   part001 = startSketchOn('XY') | ||||
|     ${startProfileAt2} | ||||
|     |> xLine(width * .5, %) | ||||
|     |> yLine(height, %) | ||||
|     |> xLine(-width * .5, %) | ||||
|     |> close(%) | ||||
|     |> hole(screwHole, %) | ||||
|     |> extrude(thickness, %) | ||||
|  | ||||
|   part002 = startSketchOn('-XZ') | ||||
|     ${startProfileAt3} | ||||
|     |> xLine(width / 4, %) | ||||
|     |> tangentialArcTo([width / 2, 0], %) | ||||
|     |> xLine(-width / 4 + wireRadius, %) | ||||
|     |> yLine(wireOffset, %) | ||||
|     |> arc({ | ||||
|           radius = wireRadius, | ||||
|           angle_start = 0, | ||||
|           angle_end = 180 | ||||
|         }, %) | ||||
|     |> yLine(-wireOffset, %) | ||||
|     |> xLine(-width / 4, %) | ||||
|     |> close(%) | ||||
|     |> extrude(-height, %) | ||||
|   ` | ||||
|     width = 20 | ||||
|     height = 10 | ||||
|     thickness = 5 | ||||
|     screwRadius = 3 | ||||
|     wireRadius = 2 | ||||
|     wireOffset = 0.5 | ||||
|    | ||||
|     screwHole = startSketchOn('XY') | ||||
|   ${startProfileAt1} | ||||
|   |> arc({ | ||||
|         radius = screwRadius, | ||||
|         angle_start = 0, | ||||
|         angle_end = 360 | ||||
|       }, %) | ||||
|    | ||||
|     part001 = startSketchOn('XY') | ||||
|   ${startProfileAt2} | ||||
|   |> xLine(width * .5, %) | ||||
|   |> yLine(height, %) | ||||
|   |> xLine(-width * .5, %) | ||||
|   |> close(%) | ||||
|   |> hole(screwHole, %) | ||||
|   |> extrude(thickness, %) | ||||
|    | ||||
|     part002 = startSketchOn('-XZ') | ||||
|   ${startProfileAt3} | ||||
|   |> xLine(width / 4, %) | ||||
|   |> tangentialArcTo([width / 2, 0], %) | ||||
|   |> xLine(-width / 4 + wireRadius, %) | ||||
|   |> yLine(wireOffset, %) | ||||
|   |> arc({ | ||||
|         radius = wireRadius, | ||||
|         angle_start = 0, | ||||
|         angle_end = 180 | ||||
|       }, %) | ||||
|   |> yLine(-wireOffset, %) | ||||
|   |> xLine(-width / 4, %) | ||||
|   |> close(%) | ||||
|   |> extrude(-height, %) | ||||
|     ` | ||||
|         ) | ||||
|       }, | ||||
|       selectionsSnippets | ||||
|     ) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -108,24 +101,22 @@ test.describe('Sketch tests', () => { | ||||
|   }) | ||||
|   test('Can delete most of a sketch and the line tool will still work', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([4.61, -14.01], %) | ||||
|   |> xLine(12.73, %) | ||||
|   |> tangentialArcTo([24.95, -5.38], %)` | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> xLine(12.73, %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await expect(async () => { | ||||
|       await page.mouse.click(700, 200) | ||||
|       await page.getByText('tangentialArcTo([24.95, -5.38], %)').click() | ||||
|       await expect( | ||||
|         page.getByRole('button', { name: 'Edit Sketch' }) | ||||
| @ -142,8 +133,7 @@ test.describe('Sketch tests', () => { | ||||
|     await page.keyboard.press('Home') | ||||
|     await page.keyboard.up('Shift') | ||||
|     await page.keyboard.press('Backspace') | ||||
|     await u.openAndClearDebugPanel() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
| @ -151,26 +141,31 @@ test.describe('Sketch tests', () => { | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await expect(async () => { | ||||
|       await page.mouse.move(700, 200, { steps: 25 }) | ||||
|       await page.mouse.click(700, 200) | ||||
|  | ||||
|       await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) | ||||
|         .toBe(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([12.34, -12.34], %) | ||||
|   |> yLine(12.34, %) | ||||
|  | ||||
| `) | ||||
|       await expect | ||||
|         .poll(u.crushKclCodeIntoOneLineAndThenMaybeSome, { timeout: 1000 }) | ||||
|         .toBe( | ||||
|           `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([4.61,-14.01], %) | ||||
|   |> yLine(15.95, %) | ||||
| ` | ||||
|             .replaceAll(' ', '') | ||||
|             .replaceAll('\n', '') | ||||
|         ) | ||||
|     }).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 | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem('persistCode', ``) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|     await expect( | ||||
| @ -187,6 +182,7 @@ test.describe('Sketch tests', () => { | ||||
|   test.describe('Can edit segments by dragging their handles', () => { | ||||
|     const doEditSegmentsByDraggingHandle = async ( | ||||
|       page: Page, | ||||
|       homePage: HomePageFixture, | ||||
|       openPanes: string[] | ||||
|     ) => { | ||||
|       // Load the app with the code panes | ||||
| @ -202,9 +198,8 @@ test.describe('Sketch tests', () => { | ||||
|       }) | ||||
|  | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await expect( | ||||
|         page.getByRole('button', { name: 'Start Sketch' }) | ||||
|       ).not.toBeDisabled() | ||||
| @ -318,7 +313,7 @@ test.describe('Sketch tests', () => { | ||||
|       |> line([1.97, 2.06], %) | ||||
|       |> close(%)`) | ||||
|     } | ||||
|     test('code pane open at start-handles', async ({ page }) => { | ||||
|     test('code pane open at start-handles', async ({ page, homePage }) => { | ||||
|       // Load the app with the code panes | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
| @ -331,10 +326,10 @@ test.describe('Sketch tests', () => { | ||||
|           }) | ||||
|         ) | ||||
|       }) | ||||
|       await doEditSegmentsByDraggingHandle(page, ['code']) | ||||
|       await doEditSegmentsByDraggingHandle(page, homePage, ['code']) | ||||
|     }) | ||||
|  | ||||
|     test('code pane closed at start-handles', async ({ page }) => { | ||||
|     test('code pane closed at start-handles', async ({ page, homePage }) => { | ||||
|       // Load the app with the code panes | ||||
|       await page.addInitScript(async (persistModelingContext) => { | ||||
|         localStorage.setItem( | ||||
| @ -342,25 +337,26 @@ test.describe('Sketch tests', () => { | ||||
|           JSON.stringify({ openPanes: [] }) | ||||
|         ) | ||||
|       }, PERSIST_MODELING_CONTEXT) | ||||
|       await doEditSegmentsByDraggingHandle(page, []) | ||||
|       await doEditSegmentsByDraggingHandle(page, homePage, []) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test('Can edit a circle center and radius by dragging its handles', async ({ | ||||
|     page, | ||||
|     editor, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> circle({ center = [4.61, -5.01], radius = 8 }, %)` | ||||
|     |> circle({ center = [4.61, -5.01], radius = 8 }, %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
| @ -399,6 +395,7 @@ test.describe('Sketch tests', () => { | ||||
|     ).toBeVisible() | ||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|     await page.waitForTimeout(400) | ||||
|  | ||||
|     let prevContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|     await expect(page.getByTestId('segment-overlay')).toHaveCount(1) | ||||
| @ -409,7 +406,9 @@ test.describe('Sketch tests', () => { | ||||
|         targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX }, | ||||
|       }) | ||||
|       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() | ||||
|     }) | ||||
|  | ||||
| @ -422,35 +421,36 @@ test.describe('Sketch tests', () => { | ||||
|         sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, | ||||
|         targetPosition: { x: lineEnd.x + dragPX * 2, y: lineEnd.y + dragPX }, | ||||
|       }) | ||||
|       await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||
|       await editor.expectEditor.not.toContain(prevContent) | ||||
|       prevContent = await page.locator('.cm-content').innerText() | ||||
|     }) | ||||
|  | ||||
|     // expect the code to have changed | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|   |> circle({ center = [7.26, -2.37], radius = 11.44 }, %) | ||||
| `) | ||||
|     await editor.expectEditor.toContain( | ||||
|       `sketch001 = startSketchOn('XZ') | ||||
|     |> 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 ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -10.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -0.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)` | ||||
|   |> startProfileAt([4.61, -10.01], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -0.38], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
| @ -504,11 +504,11 @@ test.describe('Sketch tests', () => { | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') | ||||
|     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 }, | ||||
|       sourcePosition: { x: lineEnd.x - 15, y: lineEnd.y }, | ||||
|       targetPosition: { x: lineEnd.x, y: lineEnd.y + 15 }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||
|     prevContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
| @ -517,8 +517,8 @@ test.describe('Sketch tests', () => { | ||||
|     await page.dragAndDrop('#stream', '#stream', { | ||||
|       sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 }, | ||||
|       targetPosition: { | ||||
|         x: tangentEnd.x + dragPX, | ||||
|         y: tangentEnd.y + dragPX, | ||||
|         x: tangentEnd.x, | ||||
|         y: tangentEnd.y - 15, | ||||
|       }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
| @ -527,33 +527,33 @@ test.describe('Sketch tests', () => { | ||||
|     // expect the code to have changed | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([7.12, -12.68], %) | ||||
|   |> line([15.39, -2.78], %) | ||||
|   |> tangentialArcTo([27.6, -3.05], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %) | ||||
| `) | ||||
|     |> startProfileAt([7.12, -12.68], %) | ||||
|     |> line([12.68, -1.09], %) | ||||
|     |> tangentialArcTo([24.89, 0.68], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %) | ||||
|   `) | ||||
|   }) | ||||
|  | ||||
|   test('Can edit a sketch that has been revolved in the same pipe', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%) | ||||
|     |> revolve({ axis = "X",}, %)` | ||||
|   |> startProfileAt([4.61, -14.01], %) | ||||
|   |> line([12.73, -0.09], %) | ||||
|   |> tangentialArcTo([24.95, -5.38], %) | ||||
|   |> close(%) | ||||
|   |> revolve({ axis = "X",}, %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
| @ -629,19 +629,20 @@ test.describe('Sketch tests', () => { | ||||
|     // expect the code to have changed | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([6.44, -12.07], %) | ||||
|     |> line([14.72, 1.97], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> line([1.97, 2.06], %) | ||||
|     |> close(%) | ||||
|     |> revolve({ axis = "X" }, %)`) | ||||
|   |> startProfileAt([6.44, -12.07], %) | ||||
|   |> line([14.72, 1.97], %) | ||||
|   |> tangentialArcTo([24.95, -5.38], %) | ||||
|   |> line([1.97, 2.06], %) | ||||
|   |> close(%) | ||||
|   |> revolve({ axis = "X" }, %)`) | ||||
|   }) | ||||
|   test('Can add multiple sketches', async ({ page }) => { | ||||
|   test('Can add multiple sketches', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     const viewportSize = { width: 1200, height: 500 } | ||||
|     await page.setViewportSize(viewportSize) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     const viewportSize = { width: 1200, height: 500 } | ||||
|     await page.setBodyDimensions(viewportSize) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 } | ||||
| @ -736,9 +737,8 @@ test.describe('Sketch tests', () => { | ||||
|       scale = 1 | ||||
|     ) => { | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await u.openDebugPanel() | ||||
|  | ||||
|       const code = `sketch001 = startSketchOn('-XZ') | ||||
| @ -820,36 +820,39 @@ test.describe('Sketch tests', () => { | ||||
|       await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|       await u.removeCurrentCode() | ||||
|     } | ||||
|     test('[0, 100, 100]', async ({ page }) => { | ||||
|     test('[0, 100, 100]', async ({ page, homePage }) => { | ||||
|       await homePage.goToModelingScene() | ||||
|       await doSnapAtDifferentScales(page, [0, 100, 100], 0.01) | ||||
|     }) | ||||
|  | ||||
|     test('[0, 10000, 10000]', async ({ page }) => { | ||||
|     test('[0, 10000, 10000]', async ({ page, homePage }) => { | ||||
|       await homePage.goToModelingScene() | ||||
|       await doSnapAtDifferentScales(page, [0, 10000, 10000]) | ||||
|     }) | ||||
|   }) | ||||
|   test('exiting a close extrude, has the extrude button enabled ready to go', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-0.45, 0.87], %) | ||||
|   |> line([1.32, 0.38], %) | ||||
|   |> line([1.02, -1.32], %, $seg01) | ||||
|   |> line([-1.01, -0.77], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
| |> close(%) | ||||
| ` | ||||
|     |> startProfileAt([-0.45, 0.87], %) | ||||
|     |> line([1.32, 0.38], %) | ||||
|     |> line([1.02, -1.32], %, $seg01) | ||||
|     |> line([-1.01, -0.77], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
|   ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -885,27 +888,30 @@ test.describe('Sketch tests', () => { | ||||
|       timeout: 10_000, | ||||
|     }) | ||||
|   }) | ||||
|   test("Existing sketch with bad code delete user's code", async ({ page }) => { | ||||
|   test("Existing sketch with bad code delete user's code", async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // this was a regression https://github.com/KittyCAD/modeling-app/issues/2832 | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-0.45, 0.87], %) | ||||
|   |> line([1.32, 0.38], %) | ||||
|   |> line([1.02, -1.32], %, $seg01) | ||||
|   |> line([-1.01, -0.77], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(5, sketch001) | ||||
| ` | ||||
|     |> startProfileAt([-0.45, 0.87], %) | ||||
|     |> line([1.32, 0.38], %) | ||||
|     |> line([1.02, -1.32], %, $seg01) | ||||
|     |> line([-1.01, -0.77], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(5, sketch001) | ||||
|   ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -931,16 +937,16 @@ extrude001 = extrude(5, sketch001) | ||||
|  | ||||
|     await expect((await u.codeLocator.innerText()).replace(/\s/g, '')).toBe( | ||||
|       `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-0.45, 0.87], %) | ||||
|   |> line([1.32, 0.38], %) | ||||
|   |> line([1.02, -1.32], %, $seg01) | ||||
|   |> line([-1.01, -0.77], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(5, sketch001) | ||||
| sketch002 = startSketchOn(extrude001, 'END') | ||||
|   |> | ||||
| `.replace(/\s/g, '') | ||||
|     |> startProfileAt([-0.45, 0.87], %) | ||||
|     |> line([1.32, 0.38], %) | ||||
|     |> line([1.02, -1.32], %, $seg01) | ||||
|     |> line([-1.01, -0.77], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(5, sketch001) | ||||
|   sketch002 = startSketchOn(extrude001, 'END') | ||||
|     |> | ||||
|   `.replace(/\s/g, '') | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
| @ -1049,12 +1055,8 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|  | ||||
|   test('empty-scene default-planes act as expected', async ({ | ||||
|     page, | ||||
|     browserName, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     test.skip( | ||||
|       browserName === 'webkit', | ||||
|       'Skip on Safari until `window.tearDown` is working there' | ||||
|     ) | ||||
|     /** | ||||
|      * Tests the following things | ||||
|      * 1) The the planes are there on load because the scene is empty | ||||
| @ -1067,9 +1069,7 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|      */ | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -1092,11 +1092,11 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|     await u.openAndClearDebugPanel() | ||||
|  | ||||
|     await u.codeLocator.fill(`sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> xLine(-20, %) | ||||
| `) | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> xLine(-20, %) | ||||
|   `) | ||||
|  | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|  | ||||
| @ -1117,7 +1117,7 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|  | ||||
|     // click start Sketch | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 5 }) | ||||
|     await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 }) | ||||
|     const hoveredColor: [number, number, number] = [93, 93, 127] | ||||
|     // now that we're expecting the user to select a plan, it does respond to hover | ||||
|     await expect | ||||
| @ -1131,21 +1131,19 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|     await page.waitForTimeout(200) | ||||
|     await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50) | ||||
|     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([11.8, 9.09], %) | ||||
|   |> line([3.39, -3.39], %) | ||||
| `) | ||||
|     |> startProfileAt([11.8, 9.09], %) | ||||
|     |> line([3.39, -3.39], %) | ||||
|   `) | ||||
|  | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([11.8, 9.09], %) | ||||
|   |> line([3.39, -3.39], %) | ||||
| ` | ||||
|     |> startProfileAt([11.8, 9.09], %) | ||||
|     |> line([3.39, -3.39], %) | ||||
|   ` | ||||
|       ) | ||||
|     }) | ||||
|     await page.reload() | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -1157,41 +1155,34 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|     ).toBeLessThan(3) | ||||
|   }) | ||||
|  | ||||
|   test('Can attempt to sketch on revolved face', async ({ | ||||
|     page, | ||||
|     browserName, | ||||
|   }) => { | ||||
|     test.skip( | ||||
|       browserName === 'webkit', | ||||
|       'Skip on Safari until `window.tearDown` is working there' | ||||
|     ) | ||||
|   test('Can attempt to sketch on revolved face', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `lugHeadLength = 0.25 | ||||
|         lugDiameter = 0.5 | ||||
|         lugLength = 2 | ||||
|  | ||||
|         fn lug = (origin, length, diameter, plane) => { | ||||
|           lugSketch = startSketchOn(plane) | ||||
|             |> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %) | ||||
|             |> angledLineOfYLength({ angle = 60, length = lugHeadLength }, %) | ||||
|             |> xLineTo(0 + .001, %) | ||||
|             |> yLineTo(0, %) | ||||
|             |> close(%) | ||||
|             |> revolve({ axis = "Y" }, %) | ||||
|  | ||||
|           return lugSketch | ||||
|         } | ||||
|  | ||||
|         lug([0, 0], 10, .5, "XY")` | ||||
|       lugDiameter = 0.5 | ||||
|       lugLength = 2 | ||||
|    | ||||
|       fn lug = (origin, length, diameter, plane) => { | ||||
|         lugSketch = startSketchOn(plane) | ||||
|           |> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %) | ||||
|           |> angledLineOfYLength({ angle = 60, length = lugHeadLength }, %) | ||||
|           |> xLineTo(0 + .001, %) | ||||
|           |> yLineTo(0, %) | ||||
|           |> close(%) | ||||
|           |> revolve({ axis = "Y" }, %) | ||||
|    | ||||
|         return lugSketch | ||||
|       } | ||||
|    | ||||
|       lug([0, 0], 10, .5, "XY")` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -1223,9 +1214,10 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|  | ||||
|   test('Can sketch on face when user defined function was used in the sketch', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     // 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. | ||||
| @ -1235,51 +1227,51 @@ sketch002 = startSketchOn(extrude001, 'END') | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `fn in2mm = (inches) => { | ||||
|   return inches * 25.4 | ||||
| } | ||||
|  | ||||
| const railTop = in2mm(.748) | ||||
| const railSide = in2mm(.024) | ||||
| const railBaseWidth = in2mm(.612) | ||||
| const railWideWidth = in2mm(.835) | ||||
| const railBaseLength = in2mm(.200) | ||||
| const railClampable = in2mm(.200) | ||||
|  | ||||
| const rail = startSketchOn('XZ') | ||||
|   |> startProfileAt([ | ||||
|        -railTop / 2, | ||||
|        railClampable + railBaseLength | ||||
|      ], %) | ||||
|   |> lineTo([ | ||||
|        railTop / 2, | ||||
|        railClampable + railBaseLength | ||||
|      ], %) | ||||
|   |> lineTo([ | ||||
|        railWideWidth / 2, | ||||
|        railClampable / 2 + railBaseLength | ||||
|      ], %, $seg01) | ||||
|   |> lineTo([railTop / 2, railBaseLength], %) | ||||
|   |> lineTo([railBaseWidth / 2, railBaseLength], %) | ||||
|   |> lineTo([railBaseWidth / 2, 0], %) | ||||
|   |> lineTo([-railBaseWidth / 2, 0], %) | ||||
|   |> lineTo([-railBaseWidth / 2, railBaseLength], %) | ||||
|   |> lineTo([-railTop / 2, railBaseLength], %) | ||||
|   |> lineTo([ | ||||
|        -railWideWidth / 2, | ||||
|        railClampable / 2 + railBaseLength | ||||
|      ], %) | ||||
|   |> lineTo([ | ||||
|        -railTop / 2, | ||||
|        railClampable + railBaseLength | ||||
|      ], %) | ||||
|   |> close(%) | ||||
|   |> extrude(in2mm(2), %)` | ||||
|     return inches * 25.4 | ||||
|   } | ||||
|    | ||||
|   const railTop = in2mm(.748) | ||||
|   const railSide = in2mm(.024) | ||||
|   const railBaseWidth = in2mm(.612) | ||||
|   const railWideWidth = in2mm(.835) | ||||
|   const railBaseLength = in2mm(.200) | ||||
|   const railClampable = in2mm(.200) | ||||
|    | ||||
|   const rail = startSketchOn('XZ') | ||||
|     |> startProfileAt([ | ||||
|      -railTop / 2, | ||||
|      railClampable + railBaseLength | ||||
|    ], %) | ||||
|     |> lineTo([ | ||||
|      railTop / 2, | ||||
|      railClampable + railBaseLength | ||||
|    ], %) | ||||
|     |> lineTo([ | ||||
|      railWideWidth / 2, | ||||
|      railClampable / 2 + railBaseLength | ||||
|    ], %, $seg01) | ||||
|     |> lineTo([railTop / 2, railBaseLength], %) | ||||
|     |> lineTo([railBaseWidth / 2, railBaseLength], %) | ||||
|     |> lineTo([railBaseWidth / 2, 0], %) | ||||
|     |> lineTo([-railBaseWidth / 2, 0], %) | ||||
|     |> lineTo([-railBaseWidth / 2, railBaseLength], %) | ||||
|     |> lineTo([-railTop / 2, railBaseLength], %) | ||||
|     |> lineTo([ | ||||
|      -railWideWidth / 2, | ||||
|      railClampable / 2 + railBaseLength | ||||
|    ], %) | ||||
|     |> lineTo([ | ||||
|      -railTop / 2, | ||||
|      railClampable + railBaseLength | ||||
|    ], %) | ||||
|     |> close(%) | ||||
|     |> extrude(in2mm(2), %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const center = { x: 600, y: 250 } | ||||
|     const rectangleSize = 20 | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // Start a sketch | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
| @ -1318,27 +1310,33 @@ const rail = startSketchOn('XZ') | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test2.describe('Sketch mode should be toleratant to syntax errors', () => { | ||||
|   test2( | ||||
| test.describe('Sketch mode should be toleratant to syntax errors', () => { | ||||
|   test( | ||||
|     'adding a syntax error, recovers after fixing', | ||||
|     { tag: ['@skipWin'] }, | ||||
|     async ({ app, scene, editor, toolbar }) => { | ||||
|       test.skip( | ||||
|         process.platform === 'win32', | ||||
|         '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' | ||||
|     async ({ page, homePage, context, scene, editor, toolbar }) => { | ||||
|       const file = await fs.readFile( | ||||
|         path.resolve( | ||||
|           __dirname, | ||||
|           '../../', | ||||
|           './src/wasm-lib/tests/executor/inputs/e2e-can-sketch-on-chamfer.kcl' | ||||
|         ), | ||||
|         'utf-8' | ||||
|       ) | ||||
|       const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl') | ||||
|       await app.initialise(file) | ||||
|       await context.addInitScript((file) => { | ||||
|         localStorage.setItem('persistCode', file) | ||||
|       }, file) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       const [objClick] = scene.makeMouseHelpers(600, 250) | ||||
|       const arrowHeadLocation = { x: 604, y: 129 } as const | ||||
|       const arrowHeadLocation = { x: 706, y: 129 } as const | ||||
|       const arrowHeadWhite: [number, number, number] = [255, 255, 255] | ||||
|       const backgroundGray: [number, number, number] = [28, 28, 28] | ||||
|       const verifyArrowHeadColor = async (c: [number, number, number]) => | ||||
|         scene.expectPixelColor(c, arrowHeadLocation, 15) | ||||
|  | ||||
|       await test.step('check chamfer selection changes cursor positon', async () => { | ||||
|         await expect2(async () => { | ||||
|         await expect(async () => { | ||||
|           // sometimes initial click doesn't register | ||||
|           await objClick() | ||||
|           await editor.expectActiveLinesToBe([ | ||||
| @ -1374,48 +1372,56 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => { | ||||
|         // this checks sketch segments have been drawn | ||||
|         await verifyArrowHeadColor(arrowHeadWhite) | ||||
|       }) | ||||
|       await app.page.waitForTimeout(100) | ||||
|       await page.waitForTimeout(100) | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| test2.describe(`Sketching with offset planes`, () => { | ||||
|   test2( | ||||
|     `Can select an offset plane to sketch on`, | ||||
|     async ({ app, scene, toolbar, editor }) => { | ||||
|       // We seed the scene with a single offset plane | ||||
|       await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`) | ||||
| test.describe(`Sketching with offset planes`, () => { | ||||
|   test(`Can select an offset plane to sketch on`, async ({ | ||||
|     context, | ||||
|     page, | ||||
|     scene, | ||||
|     toolbar, | ||||
|     editor, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // We seed the scene with a single offset plane | ||||
|     await context.addInitScript(() => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `offsetPlane001 = offsetPlane("XY", 10)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|       const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200) | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|       await test2.step(`Start sketching on the offset plane`, async () => { | ||||
|         await toolbar.startSketchPlaneSelection() | ||||
|     const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200) | ||||
|  | ||||
|         await test2.step(`Hovering should highlight code`, async () => { | ||||
|           await planeHover() | ||||
|           await editor.expectState({ | ||||
|             activeLines: [`offsetPlane001=offsetPlane("XY",10)`], | ||||
|             diagnostics: [], | ||||
|             highlightedCode: 'offsetPlane("XY", 10)', | ||||
|           }) | ||||
|     await test.step(`Start sketching on the offset plane`, async () => { | ||||
|       await toolbar.startSketchPlaneSelection() | ||||
|  | ||||
|       await test.step(`Hovering should highlight code`, async () => { | ||||
|         await planeHover() | ||||
|         await editor.expectState({ | ||||
|           activeLines: [`offsetPlane001=offsetPlane("XY",10)`], | ||||
|           diagnostics: [], | ||||
|           highlightedCode: 'offsetPlane("XY", 10)', | ||||
|         }) | ||||
|  | ||||
|         await test2.step( | ||||
|           `Clicking should select the plane and enter sketch mode`, | ||||
|           async () => { | ||||
|             await planeClick() | ||||
|             // Have to wait for engine-side animation to finish | ||||
|             await app.page.waitForTimeout(600) | ||||
|             await expect2(toolbar.lineBtn).toBeEnabled() | ||||
|             await editor.expectEditor.toContain('startSketchOn(offsetPlane001)') | ||||
|             await editor.expectState({ | ||||
|               activeLines: [`offsetPlane001=offsetPlane("XY",10)`], | ||||
|               diagnostics: [], | ||||
|               highlightedCode: '', | ||||
|             }) | ||||
|           } | ||||
|         ) | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|       await test.step(`Clicking should select the plane and enter sketch mode`, async () => { | ||||
|         await planeClick() | ||||
|         // Have to wait for engine-side animation to finish | ||||
|         await page.waitForTimeout(600) | ||||
|         await expect(toolbar.lineBtn).toBeEnabled() | ||||
|         await editor.expectEditor.toContain('startSketchOn(offsetPlane001)') | ||||
|         await editor.expectState({ | ||||
|           activeLines: [`offsetPlane001=offsetPlane("XY",10)`], | ||||
|           diagnostics: [], | ||||
|           highlightedCode: '', | ||||
|         }) | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| @ -47,7 +47,12 @@ test.beforeEach(async ({ page }) => { | ||||
|  | ||||
| test.setTimeout(60_000) | ||||
|  | ||||
| test( | ||||
|  | ||||
| // We test this end to end already - getting this to work on web just to take | ||||
| // 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', | ||||
|   { tag: ['@snapshot', '@skipWin', '@skipMacos'] }, | ||||
|   async ({ page, context }) => { | ||||
|  | ||||
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 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: 49 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB | 
| Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB | 
| @ -109,242 +109,21 @@ keychain = startSketchOn("XY") | ||||
|   |> close(%) | ||||
|   |> extrude(thickness, %) | ||||
|  | ||||
| // generated from  /home/paultag/Downloads/zma-logomark.svg | ||||
| fn svg = (surface, origin, depth) => { | ||||
|   let a0 = surface |> startProfileAt([origin[0] + 45.430427, origin[1] + -14.627736], %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0, 0.764157 ], | ||||
|     control2: [ 0, 1.528314 ], | ||||
|     to: [ 0, 2.292469 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -3.03202, 0 ], | ||||
|     control2: [ -6.064039, 0 ], | ||||
|     to: [ -9.09606, 0 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0, -1.077657 ], | ||||
|     control2: [ 0, -2.155312 ], | ||||
|     to: [ 0, -3.232969 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.741805, 0 ], | ||||
|     control2: [ 5.483613, 0 ], | ||||
|     to: [ 8.225417, 0 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.740682, -2.961815 ], | ||||
|     control2: [ -5.490342, -5.925794 ], | ||||
|     to: [ -8.225417, -8.886255 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0, -0.723995 ], | ||||
|     control2: [ 0, -1.447988 ], | ||||
|     to: [ 0, -2.171981 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.712124, 0.05061 ], | ||||
|     control2: [ 1.511636, -0.09877 ], | ||||
|     to: [ 2.172096, 0.07005 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.68573, 0.740811 ], | ||||
|     control2: [ 1.371459, 1.481622 ], | ||||
|     to: [ 2.057187, 2.222436 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0, -0.76416 ], | ||||
|     control2: [ 0, -1.52832 ], | ||||
|     to: [ 0, -2.29248 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 3.032013, 0 ], | ||||
|     control2: [ 6.064026, 0 ], | ||||
|     to: [ 9.096038, 0 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0, 1.077657 ], | ||||
|     control2: [ 0, 2.155314 ], | ||||
|     to: [ 0, 3.232973 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.741312, 0 ], | ||||
|     control2: [ -5.482623, 0 ], | ||||
|     to: [ -8.223936, 0 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.741313, 2.961108 ], | ||||
|     control2: [ 5.482624, 5.922216 ], | ||||
|     to: [ 8.223936, 8.883325 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0, 0.724968 ], | ||||
|     control2: [ 0, 1.449938 ], | ||||
|     to: [ 0, 2.174907 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.712656, -0.05145 ], | ||||
|     control2: [ -1.512554, 0.09643 ], | ||||
|     to: [ -2.173592, -0.07298 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.685222, -0.739834 ], | ||||
|     control2: [ -1.370445, -1.479669 ], | ||||
|     to: [ -2.055669, -2.219505 ] | ||||
|    }, %) | ||||
|     |> close(%) | ||||
|     |> extrude(depth, %) | ||||
| keychain1 = startSketchOn("XY") | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> lineTo([width, 0], %) | ||||
|   |> lineTo([width, height], %) | ||||
|   |> lineTo([0, height], %) | ||||
|   |> close(%) | ||||
|   |> extrude(thickness, %) | ||||
|  | ||||
|   let a1 = surface |> startProfileAt([origin[0] + 57.920488, origin[1] + -15.244943], %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.78904, 0.106635 ], | ||||
|     control2: [ -5.052548, -2.969529 ], | ||||
|     to: [ -4.055141, -5.598369 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.841523, -0.918736 ], | ||||
|     control2: [ 0.439412, -1.541892 ], | ||||
|     to: [ -0.368488, -2.214378 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.418245, -0.448461 ], | ||||
|     control2: [ -0.836489, -0.896922 ], | ||||
|     to: [ -1.254732, -1.345384 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.76806, 2.995359 ], | ||||
|     control2: [ -2.32667, 8.18409 ], | ||||
|     to: [ 0.897655, 10.678932 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.562822, 2.186098 ], | ||||
|     control2: [ 6.605111, 2.28043 ], | ||||
|     to: [ 9.271202, 0.226476 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.743744, -0.797465 ], | ||||
|     control2: [ -1.487487, -1.594932 ], | ||||
|     to: [ -2.231232, -2.392397 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.672938, 0.421422 ], | ||||
|     control2: [ -1.465362, 0.646946 ], | ||||
|     to: [ -2.259264, 0.64512 ] | ||||
|    }, %) | ||||
|     |> close(%) | ||||
|     |> extrude(depth, %) | ||||
|  | ||||
|   let a2 = surface |> startProfileAt([origin[0] + 62.19406300000001, origin[1] + -19.500698999999997], %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.302938, 1.281141 ], | ||||
|     control2: [ -1.53575, 2.434288 ], | ||||
|     to: [ -0.10908, 3.279477 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.504637, 0.54145 ], | ||||
|     control2: [ 1.009273, 1.082899 ], | ||||
|     to: [ 1.513909, 1.624348 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.767778, -2.995425 ], | ||||
|     control2: [ 2.327135, -8.184384 ], | ||||
|     to: [ -0.897661, -10.679047 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.562947, -2.186022 ], | ||||
|     control2: [ -6.604089, -2.279606 ], | ||||
|     to: [ -9.271196, -0.227813 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.744231, 0.797952 ], | ||||
|     control2: [ 1.488461, 1.595904 ], | ||||
|     to: [ 2.232692, 2.393856 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.302377, -1.564629 ], | ||||
|     control2: [ 5.793126, -0.15358 ], | ||||
|     to: [ 6.396577, 2.547372 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.08981, 0.346302 ], | ||||
|     control2: [ 0.134865, 0.704078 ], | ||||
|     to: [ 0.13476, 1.061807 ] | ||||
|    }, %) | ||||
|     |> close(%) | ||||
|     |> extrude(depth, %) | ||||
|  | ||||
|   let a3 = surface |> startProfileAt([origin[0] + 74.124866, origin[1] + -15.244943], %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.78904, 0.106635 ], | ||||
|     control2: [ -5.052549, -2.969529 ], | ||||
|     to: [ -4.055142, -5.598369 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.841527, -0.918738 ], | ||||
|     control2: [ 0.43941, -1.541892 ], | ||||
|     to: [ -0.368497, -2.214367 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.418254, -0.448466 ], | ||||
|     control2: [ -0.836507, -0.896931 ], | ||||
|     to: [ -1.254761, -1.345395 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.768019, 2.995371 ], | ||||
|     control2: [ -2.326624, 8.184088 ], | ||||
|     to: [ 0.897678, 10.678932 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.56289, 2.186191 ], | ||||
|     control2: [ 6.60516, 2.280307 ], | ||||
|     to: [ 9.271371, 0.226476 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.743808, -0.797465 ], | ||||
|     control2: [ -1.487616, -1.594932 ], | ||||
|     to: [ -2.231424, -2.392397 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -0.672916, 0.421433 ], | ||||
|     control2: [ -1.465344, 0.646926 ], | ||||
|     to: [ -2.259225, 0.64512 ] | ||||
|    }, %) | ||||
|     |> close(%) | ||||
|     |> extrude(depth, %) | ||||
|  | ||||
|   let a4 = surface |> startProfileAt([origin[0] + 77.57333899999998, origin[1] + -16.989262999999998], %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.743298, 0.797463 ], | ||||
|     control2: [ 1.486592, 1.594926 ], | ||||
|     to: [ 2.229888, 2.392389 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.767827, -2.995393 ], | ||||
|     control2: [ 2.327103, -8.184396 ], | ||||
|     to: [ -0.897672, -10.679047 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ -2.562939, -2.186037 ], | ||||
|     control2: [ -6.604077, -2.279589 ], | ||||
|     to: [ -9.271185, -0.227813 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.744243, 0.797952 ], | ||||
|     control2: [ 1.488486, 1.595904 ], | ||||
|     to: [ 2.232729, 2.393856 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 2.302394, -1.564623 ], | ||||
|     control2: [ 5.793201, -0.153598 ], | ||||
|     to: [ 6.396692, 2.547372 ] | ||||
|    }, %) | ||||
|     |> bezierCurve({ | ||||
|     control1: [ 0.32074, 1.215468 ], | ||||
|     control2: [ 0.06159, 2.564765 ], | ||||
|     to: [ -0.690452, 3.573243 ] | ||||
|    }, %) | ||||
|     |> close(%) | ||||
|     |> extrude(depth, %) | ||||
| keychain2 = startSketchOn("XY") | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> lineTo([width, 0], %) | ||||
|   |> lineTo([width, height], %) | ||||
|   |> lineTo([0, height], %) | ||||
|   |> close(%) | ||||
|   |> extrude(thickness, %) | ||||
|  | ||||
| box = startSketchOn('XY') | ||||
|   |> startProfileAt([0, 0], %) | ||||
| @ -354,7 +133,7 @@ box = startSketchOn('XY') | ||||
|   |> close(%) | ||||
|   |> extrude(10, %) | ||||
|  | ||||
|   sketch001 = startSketchOn(box, revolveAxis) | ||||
| sketch001 = startSketchOn(box, revolveAxis) | ||||
|   |> startProfileAt([5, 10], %) | ||||
|   |> line([0, -10], %) | ||||
|   |> line([2, 0], %) | ||||
| @ -364,18 +143,12 @@ box = startSketchOn('XY') | ||||
|   axis: revolveAxis, | ||||
|   angle: 90 | ||||
|   }, %) | ||||
|   return 0 | ||||
| } | ||||
|  | ||||
| sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0.0, 0.0], %) | ||||
|   |> xLine(0.0, %) | ||||
|   |> close(%) | ||||
|  | ||||
|  | ||||
| svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness) | ||||
|  | ||||
| startSketchOn(keychain, 'end') | ||||
|   |> circle({ center: [ | ||||
|        width / 2, | ||||
|        height - (keychainHoleSize + 1.5) | ||||
|      ], radius: keychainHoleSize }, %) | ||||
|   |> extrude(-thickness, %)` | ||||
| ` | ||||
|  | ||||
| export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `thing = 1` | ||||
|  | ||||
| @ -1,29 +1,16 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| import { commonPoints, 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) | ||||
| }) | ||||
| import { commonPoints, getUtils } from './test-utils' | ||||
|  | ||||
| test.describe('Test network and connection issues', () => { | ||||
|   test('simulate network down and network little widget', async ({ | ||||
|     page, | ||||
|     browserName, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit | ||||
|     test.skip( | ||||
|       browserName === 'webkit', | ||||
|       'Skip on Safari until `window.tearDown` is working there' | ||||
|     ) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     const networkToggle = page.getByTestId('network-toggle') | ||||
|  | ||||
| @ -62,7 +49,7 @@ test.describe('Test network and connection issues', () => { | ||||
|     }) | ||||
|  | ||||
|     // Expect the network to be down | ||||
|     await expect(networkToggle).toContainText('Offline') | ||||
|     await expect(networkToggle).toContainText('Problem') | ||||
|  | ||||
|     // Click the network widget | ||||
|     await networkWidget.click() | ||||
| @ -93,26 +80,19 @@ test.describe('Test network and connection issues', () => { | ||||
|  | ||||
|   test('Engine disconnect & reconnect in sketch mode', async ({ | ||||
|     page, | ||||
|     browserName, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit | ||||
|     test.skip( | ||||
|       browserName === 'webkit', | ||||
|       'Skip on Safari until `window.tearDown` is working there' | ||||
|     ) | ||||
|     const networkToggle = page.getByTestId('network-toggle') | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled({ timeout: 15000 }) | ||||
|  | ||||
|     // click on "Start Sketch" button | ||||
|     await u.clearCommandLogs() | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
| @ -132,7 +112,7 @@ test.describe('Test network and connection issues', () => { | ||||
|     await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt(${commonPoints.startAt}, %)`) | ||||
|   |> startProfileAt(${commonPoints.startAt}, %)`) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
| @ -140,8 +120,8 @@ test.describe('Test network and connection issues', () => { | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt(${commonPoints.startAt}, %) | ||||
|     |> xLine(${commonPoints.num1}, %)`) | ||||
|   |> startProfileAt(${commonPoints.startAt}, %) | ||||
|   |> xLine(${commonPoints.num1}, %)`) | ||||
|  | ||||
|     // Expect the network to be up | ||||
|     await expect(networkToggle).toContainText('Connected') | ||||
| @ -156,7 +136,7 @@ test.describe('Test network and connection issues', () => { | ||||
|     }) | ||||
|  | ||||
|     // Expect the network to be down | ||||
|     await expect(networkToggle).toContainText('Offline') | ||||
|     await expect(networkToggle).toContainText('Problem') | ||||
|  | ||||
|     // Ensure we are not in sketch mode | ||||
|     await expect( | ||||
|  | ||||
| @ -1,22 +1,20 @@ | ||||
| import { | ||||
|   expect, | ||||
|   Page, | ||||
|   Download, | ||||
|   BrowserContext, | ||||
|   TestInfo, | ||||
|   _electron as electron, | ||||
|   Locator, | ||||
|   test, | ||||
| } from '@playwright/test' | ||||
| import { test, Page } from './zoo-test' | ||||
| import { EngineCommand } from 'lang/std/artifactGraph' | ||||
| import fsp from 'fs/promises' | ||||
| import fsSync from 'fs' | ||||
| import { join } from 'path' | ||||
| import path from 'path' | ||||
| import pixelMatch from 'pixelmatch' | ||||
| import { PNG } from 'pngjs' | ||||
| import { Protocol } from 'playwright-core/types/protocol' | ||||
| import type { Models } from '@kittycad/lib' | ||||
| import { APP_NAME, COOKIE_NAME } from 'lib/constants' | ||||
| import { COOKIE_NAME } from 'lib/constants' | ||||
| import { secrets } from './secrets' | ||||
| import { | ||||
|   TEST_SETTINGS_KEY, | ||||
| @ -30,6 +28,10 @@ import { isErrorWhitelisted } from './lib/console-error-whitelist' | ||||
| import { isArray } from 'lib/utils' | ||||
| import { reportRejection } from 'lib/trap' | ||||
|  | ||||
| const toNormalizedCode = (text: string) => { | ||||
|   return text.replace(/\s+/g, '') | ||||
| } | ||||
|  | ||||
| type TestColor = [number, number, number] | ||||
| export const TEST_COLORS = { | ||||
|   WHITE: [249, 249, 249] as TestColor, | ||||
| @ -98,11 +100,16 @@ async function removeCurrentCode(page: Page) { | ||||
| } | ||||
|  | ||||
| export async function sendCustomCmd(page: Page, cmd: EngineCommand) { | ||||
|   await page.getByTestId('custom-cmd-input').fill(JSON.stringify(cmd)) | ||||
|   const json = JSON.stringify(cmd) | ||||
|   await page.getByTestId('custom-cmd-input').fill(json) | ||||
|   await expect(page.getByTestId('custom-cmd-input')).toHaveValue(json) | ||||
|   await page.getByTestId('custom-cmd-send-button').scrollIntoViewIfNeeded() | ||||
|   await page.getByTestId('custom-cmd-send-button').click() | ||||
| } | ||||
|  | ||||
| async function clearCommandLogs(page: Page) { | ||||
|   await page.getByTestId('custom-cmd-input').fill('') | ||||
|   await page.getByTestId('clear-commands').scrollIntoViewIfNeeded() | ||||
|   await page.getByTestId('clear-commands').click() | ||||
| } | ||||
|  | ||||
| @ -150,6 +157,19 @@ export async function closePane(page: Page, testId: string) { | ||||
|  | ||||
| async function openKclCodePanel(page: Page) { | ||||
|   await openPane(page, 'code-pane-button') | ||||
|  | ||||
|   // Code Mirror lazy loads text! Wowza! Let's force-load the text for tests. | ||||
|   await page.evaluate(() => { | ||||
|     // editorManager is available on the window object. | ||||
|     //@ts-ignore this is in an entirely different context that tsc can't see. | ||||
|     editorManager._editorView.dispatch({ | ||||
|       selection: { | ||||
|         //@ts-ignore this is in an entirely different context that tsc can't see. | ||||
|         anchor: editorManager._editorView.docView.length, | ||||
|       }, | ||||
|       scrollIntoView: true, | ||||
|     }) | ||||
|   }) | ||||
| } | ||||
|  | ||||
| async function closeKclCodePanel(page: Page) { | ||||
| @ -165,6 +185,9 @@ async function closeKclCodePanel(page: Page) { | ||||
|  | ||||
| async function openDebugPanel(page: Page) { | ||||
|   await openPane(page, 'debug-pane-button') | ||||
|  | ||||
|   // The debug pane needs time to load everything. | ||||
|   await page.waitForTimeout(3000) | ||||
| } | ||||
|  | ||||
| export async function closeDebugPanel(page: Page) { | ||||
| @ -412,6 +435,10 @@ export async function getUtils(page: Page, test_?: typeof test) { | ||||
|         .boundingBox({ timeout: 5_000 }) | ||||
|         .then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })), | ||||
|     codeLocator: page.locator('.cm-content'), | ||||
|     crushKclCodeIntoOneLineAndThenMaybeSome: async () => { | ||||
|       const code = await page.locator('.cm-content').innerText() | ||||
|       return code.replaceAll(' ', '').replaceAll('\n', '') | ||||
|     }, | ||||
|     normalisedEditorCode: async () => { | ||||
|       const code = await page.locator('.cm-content').innerText() | ||||
|       return normaliseKclNumbers(code) | ||||
| @ -482,13 +509,18 @@ export async function getUtils(page: Page, test_?: typeof test) { | ||||
|       ) | ||||
|     }, | ||||
|  | ||||
|     toNormalizedCode: (text: string) => { | ||||
|       return text.replace(/\s+/g, '') | ||||
|     toNormalizedCode(text: string) { | ||||
|       return toNormalizedCode(text) | ||||
|     }, | ||||
|  | ||||
|     editorTextMatches: async (code: string) => { | ||||
|     async editorTextMatches(code: string) { | ||||
|       const editor = page.locator(editorSelector) | ||||
|       return expect(editor).toHaveText(code, { useInnerText: true }) | ||||
|       return expect | ||||
|         .poll(async () => { | ||||
|           const text = await editor.textContent() | ||||
|           return toNormalizedCode(text ?? '') | ||||
|         }) | ||||
|         .toContain(toNormalizedCode(code)) | ||||
|     }, | ||||
|  | ||||
|     pasteCodeInEditor: async (code: string) => { | ||||
| @ -514,7 +546,7 @@ export async function getUtils(page: Page, test_?: typeof test) { | ||||
|           page.getByRole('button', { name: 'Start Sketch' }) | ||||
|         ).not.toBeDisabled() | ||||
|         await page.getByTestId('create-file-button').click() | ||||
|         await page.getByTestId('file-rename-field').fill(name) | ||||
|         await page.getByTestId('tree-input-field').fill(name) | ||||
|         await page.keyboard.press('Enter') | ||||
|       }) | ||||
|     }, | ||||
| @ -674,6 +706,34 @@ export const makeTemplate: ( | ||||
|   } | ||||
| } | ||||
|  | ||||
| const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright' | ||||
|  | ||||
| export const getPlaywrightDownloadDir = (page: Page) => { | ||||
|   return path.resolve(page.dir, PLAYWRIGHT_DOWNLOAD_DIR) | ||||
| } | ||||
|  | ||||
| const moveDownloadedFileTo = async (page: Page, toLocation: string) => { | ||||
|   await fsp.mkdir(path.dirname(toLocation), { recursive: true }) | ||||
|  | ||||
|   const downloadDir = getPlaywrightDownloadDir(page) | ||||
|  | ||||
|   // Expect there to be at least one file | ||||
|   await expect | ||||
|     .poll(async () => { | ||||
|       const files = await fsp.readdir(downloadDir) | ||||
|       return files.length | ||||
|     }) | ||||
|     .toBe(1) | ||||
|  | ||||
|   // Go through the downloads dir and move files to new location | ||||
|   const files = await fsp.readdir(downloadDir) | ||||
|  | ||||
|   // Assumption: only ever one file here. | ||||
|   for (let file of files) { | ||||
|     await fsp.rename(path.resolve(downloadDir, file), toLocation) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export interface Paths { | ||||
|   modelPath: string | ||||
|   imagePath: string | ||||
| @ -686,7 +746,8 @@ export const doExport = async ( | ||||
|   exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' | ||||
| ): Promise<Paths> => { | ||||
|   if (exportFrom === 'dropdown') { | ||||
|     await page.getByRole('button', { name: APP_NAME }).click() | ||||
|     await page.getByTestId('project-sidebar-toggle').click() | ||||
|  | ||||
|     const exportMenuButton = page.getByRole('button', { | ||||
|       name: 'Export current part', | ||||
|     }) | ||||
| @ -727,25 +788,12 @@ export const doExport = async ( | ||||
|   } | ||||
|   await expect(page.getByText('Confirm Export')).toBeVisible() | ||||
|  | ||||
|   const getPromiseAndResolve = () => { | ||||
|     let resolve: any = () => {} | ||||
|     const promise = new Promise<Download>((r) => { | ||||
|       resolve = r | ||||
|     }) | ||||
|     return [promise, resolve] | ||||
|   } | ||||
|  | ||||
|   const [downloadPromise1, downloadResolve1] = getPromiseAndResolve() | ||||
|   let downloadCnt = 0 | ||||
|  | ||||
|   if (exportFrom === 'dropdown') | ||||
|     page.on('download', async (download) => { | ||||
|       if (downloadCnt === 0) { | ||||
|         downloadResolve1(download) | ||||
|       } | ||||
|       downloadCnt++ | ||||
|     }) | ||||
|   await page.getByRole('button', { name: 'Submit command' }).click() | ||||
|  | ||||
|   // This usually happens immediately after. If we're too slow we don't | ||||
|   // catch it. | ||||
|   await expect(page.getByText('Exported successfully')).toBeVisible() | ||||
|  | ||||
|   if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') { | ||||
|     return { | ||||
|       modelPath: '', | ||||
| @ -755,15 +803,12 @@ export const doExport = async ( | ||||
|   } | ||||
|  | ||||
|   // Handle download | ||||
|   const download = await downloadPromise1 | ||||
|   const downloadLocationer = (extra = '', isImage = false) => | ||||
|     `./e2e/playwright/export-snapshots/${output.type}-${ | ||||
|       'storage' in output ? output.storage : '' | ||||
|     }${extra}.${isImage ? 'png' : output.type}` | ||||
|   const downloadLocation = downloadLocationer() | ||||
|  | ||||
|   await download.saveAs(downloadLocation) | ||||
|  | ||||
|   if (output.type === 'step') { | ||||
|     // stable timestamps for step files | ||||
|     const fileContents = await fsp.readFile(downloadLocation, 'utf-8') | ||||
| @ -772,6 +817,12 @@ export const doExport = async ( | ||||
|       '1970-01-01T00:00:00.0+00:00' | ||||
|     ) | ||||
|     await fsp.writeFile(downloadLocation, newFileContents) | ||||
|   } else { | ||||
|     // By default all files are downloaded to the same place in playwright | ||||
|     // (declared in src/lib/exportSave) | ||||
|     // To remain consistent with our old web tests, we want to move some downloads | ||||
|     // (images) to another directory. | ||||
|     await moveDownloadedFileTo(page, downloadLocation) | ||||
|   } | ||||
|  | ||||
|   return { | ||||
| @ -798,6 +849,8 @@ export async function tearDown(page: Page, testInfo: TestInfo) { | ||||
|   // It seems it's best to give the browser about 3s to close things | ||||
|   // It's not super reliable but we have no real other choice for now | ||||
|   await page.waitForTimeout(3000) | ||||
|  | ||||
|   await testInfo.tronApp?.close() | ||||
| } | ||||
|  | ||||
| // settingsOverrides may need to be augmented to take more generic items, | ||||
| @ -808,12 +861,19 @@ export async function setup( | ||||
|   testInfo?: TestInfo | ||||
| ) { | ||||
|   await context.addInitScript( | ||||
|     async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => { | ||||
|     async ({ | ||||
|       token, | ||||
|       settingsKey, | ||||
|       settings, | ||||
|       IS_PLAYWRIGHT_KEY, | ||||
|       PLAYWRIGHT_TEST_DIR, | ||||
|     }) => { | ||||
|       localStorage.clear() | ||||
|       localStorage.setItem('TOKEN_PERSIST_KEY', token) | ||||
|       localStorage.setItem('persistCode', ``) | ||||
|       localStorage.setItem(settingsKey, settings) | ||||
|       localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true') | ||||
|       localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR) | ||||
|     }, | ||||
|     { | ||||
|       token: secrets.token, | ||||
| @ -830,6 +890,7 @@ export async function setup( | ||||
|         } as Partial<SaveSettingsPayload>, | ||||
|       }), | ||||
|       IS_PLAYWRIGHT_KEY, | ||||
|       PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory, | ||||
|     } | ||||
|   ) | ||||
|  | ||||
| @ -887,13 +948,17 @@ export async function setupElectron({ | ||||
|       ? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' } | ||||
|       : {}), | ||||
|   }) | ||||
|  | ||||
|   const context = electronApp.context() | ||||
|   const page = await electronApp.firstWindow() | ||||
|  | ||||
|   page.TEST_SETTINGS_FILE_KEY = projectDirName | ||||
|  | ||||
|   context.on('console', console.log) | ||||
|   page.on('console', console.log) | ||||
|  | ||||
|   if (cleanProjectDir) { | ||||
|     const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) | ||||
|     const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME) | ||||
|     const settingsOverrides = TOML.stringify( | ||||
|       appSettings | ||||
|         ? { | ||||
| @ -924,7 +989,7 @@ export async function setupElectron({ | ||||
|  | ||||
|   await setup(context, page) | ||||
|  | ||||
|   return { electronApp, page, dir: projectDirName } | ||||
|   return { electronApp, page, context, dir: projectDirName } | ||||
| } | ||||
|  | ||||
| function failOnConsoleErrors(page: Page, testInfo?: TestInfo) { | ||||
| @ -1010,7 +1075,7 @@ export async function createProject({ | ||||
| } | ||||
|  | ||||
| export function executorInputPath(fileName: string): string { | ||||
|   return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName) | ||||
|   return path.join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName) | ||||
| } | ||||
|  | ||||
| export async function doAndWaitForImageDiff( | ||||
| @ -1101,3 +1166,12 @@ export function getPixelRGBs(page: Page) { | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export async function pollEditorLinesSelectedLength(page: Page, lines: number) { | ||||
|   return expect | ||||
|     .poll(async () => { | ||||
|       const lines = await page.locator('.cm-activeLine').all() | ||||
|       return lines.length | ||||
|     }) | ||||
|     .toBe(lines) | ||||
| } | ||||
|  | ||||
| @ -1,23 +1,14 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
| import { EngineCommand } from 'lang/std/artifactGraph' | ||||
| import { uuidv4 } from 'lib/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) | ||||
| }) | ||||
| import { getUtils } from './test-utils' | ||||
|  | ||||
| test.describe('Testing Camera Movement', () => { | ||||
|   test('Can move camera reliably', async ({ page, context }) => { | ||||
|     test.skip(process.platform === 'darwin', 'Can move camera reliably') | ||||
|   test('Can move camera reliably', async ({ page, context, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await u.closeKclCodePanel() | ||||
|  | ||||
| @ -183,6 +174,7 @@ test.describe('Testing Camera Movement', () => { | ||||
|  | ||||
|   test('Zoom should be consistent when exiting or entering sketches', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     // start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place | ||||
|     // than zoom and pan outside of sketch mode and enter again and it should not change from where it is | ||||
| @ -190,9 +182,9 @@ test.describe('Testing Camera Movement', () => { | ||||
|  | ||||
|     test.skip(process.platform !== 'darwin', 'Zoom should be consistent') | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     await expect( | ||||
| @ -344,7 +336,10 @@ test.describe('Testing Camera Movement', () => { | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test(`Zoom by scroll should not fire while orbiting`, async ({ page }) => { | ||||
|   test(`Zoom by scroll should not fire while orbiting`, async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     /** | ||||
|      * Currently we only allow zooming by scroll when no other camera movement is happening, | ||||
|      * set within cameraMouseDragGuards in cameraControls.ts, | ||||
| @ -383,7 +378,7 @@ test.describe('Testing Camera Movement', () => { | ||||
|     const expectedOrbitCamZPosition = 64.0 | ||||
|  | ||||
|     await test.step(`Test setup`, async () => { | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.closeKclCodePanel() | ||||
|       // This test requires the mouse controls to be set to Solidworks | ||||
|       await u.openDebugPanel() | ||||
|  | ||||
| @ -1,35 +1,32 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| import { getUtils, setup, tearDown, TEST_COLORS } from './test-utils' | ||||
| import { | ||||
|   getUtils, | ||||
|   TEST_COLORS, | ||||
|   pollEditorLinesSelectedLength, | ||||
| } from './test-utils' | ||||
| import { XOR } 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('Testing constraints', () => { | ||||
|   test('Can constrain line length', async ({ page }) => { | ||||
|   test('Can constrain line length', async ({ page, homePage }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> xLine(-20, %) | ||||
|   ` | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> xLine(-20, %) | ||||
|     ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -41,7 +38,9 @@ test.describe('Testing constraints', () => { | ||||
|  | ||||
|     // enter sketch again | ||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|     await page.waitForTimeout(500) // wait for animation | ||||
|  | ||||
|     // Wait for overlays to populate | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     const startXPx = 500 | ||||
|     await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) | ||||
| @ -67,38 +66,44 @@ test.describe('Testing constraints', () => { | ||||
|  | ||||
|     // Exit sketch | ||||
|     await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) | ||||
|     await page.keyboard.press('Escape') | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Exit Sketch' }) | ||||
|     ).not.toBeVisible() | ||||
|     await expect | ||||
|       .poll(async () => { | ||||
|         await page.keyboard.press('Escape', { delay: 500 }) | ||||
|         return page.getByRole('button', { name: 'Exit Sketch' }).isVisible() | ||||
|       }) | ||||
|       .toBe(true) | ||||
|   }) | ||||
|   test(`Remove constraints`, async ({ page }) => { | ||||
|   test(`Remove constraints`, async ({ page, homePage }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `yo = 79 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %, $seg01) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> angledLine([segAng(seg01), yo], %) | ||||
|   |> line([41.19, 58.97 + 5], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 120], %) | ||||
|   |> xLine(-385.34, %, $seg_what) | ||||
|   |> yLine(-170.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|   part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([-7.54, -26.74], %) | ||||
|     |> line([74.36, 130.4], %, $seg01) | ||||
|     |> line([78.92, -120.11], %) | ||||
|     |> angledLine([segAng(seg01), yo], %) | ||||
|     |> line([41.19, 58.97 + 5], %) | ||||
|   part002 = startSketchOn('XZ') | ||||
|     |> startProfileAt([299.05, 120], %) | ||||
|     |> xLine(-385.34, %, $seg_what) | ||||
|     |> yLine(-170.06, %) | ||||
|     |> xLine(segLen(seg_what), %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       ) | ||||
|     }) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await page.getByText('line([74.36, 130.4], %, $seg01)').click() | ||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|     // Wait for overlays to populate | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`) | ||||
|  | ||||
|     await page.mouse.click(line3.x, line3.y) | ||||
| @ -111,8 +116,8 @@ part002 = startSketchOn('XZ') | ||||
|     await page.getByRole('button', { name: 'remove constraints' }).click() | ||||
|  | ||||
|     await page.getByText('line([39.13, 68.63], %)').click() | ||||
|     await pollEditorLinesSelectedLength(page, 1) | ||||
|     const activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|     await expect(activeLinesContent).toHaveLength(1) | ||||
|     await expect(activeLinesContent[0]).toHaveText('|> line([39.13, 68.63], %)') | ||||
|  | ||||
|     // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state | ||||
| @ -130,43 +135,75 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { testName, offset } of cases) { | ||||
|       test(`${testName}`, async ({ page }) => { | ||||
|       test(`${testName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %, $seg01) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> angledLine([segAng(seg01), 78.33], %) | ||||
|   |> line([51.19, 48.97], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %, $seg01) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> angledLine([segAng(seg01), 78.33], %) | ||||
|         |> line([51.19, 48.97], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|  | ||||
|           const isChecked = await createNewVariableCheckbox.isChecked() | ||||
|           const addVariable = testName === 'Add variable' | ||||
|           XOR(isChecked, addVariable) && // XOR because no need to click the checkbox if the state is already correct | ||||
|             (await createNewVariableCheckbox.click()) | ||||
|  | ||||
|           await page | ||||
|             .getByRole('button', { name: 'Add constraining value' }) | ||||
|             .click() | ||||
|  | ||||
|           // Wait for the codemod to take effect | ||||
|           await expect(page.locator('.cm-content')).toContainText(`angle: -57,`) | ||||
|           await expect(page.locator('.cm-content')).toContainText( | ||||
|             `offset: ${offset},` | ||||
|           ) | ||||
|  | ||||
|           await pollEditorLinesSelectedLength(page, 2) | ||||
|           const activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|           await expect(activeLinesContent[0]).toHaveText( | ||||
|             `|> line([74.36, 130.4], %, $seg01)` | ||||
|           ) | ||||
|           await expect(activeLinesContent[1]).toHaveText(`}, %)`) | ||||
|  | ||||
|           // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state | ||||
|           await expect(page.getByTestId('segment-overlay')).toHaveCount(4) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %, $seg01)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Give time for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const [line1, line3] = await Promise.all([ | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), | ||||
|         ]) | ||||
|  | ||||
|         await page.mouse.click(line1.x, line1.y) | ||||
|         await page.keyboard.down('Shift') | ||||
|         await page.mouse.click(line3.x, line3.y) | ||||
|         await page.waitForTimeout(100) // this wait is needed for webkit - not sure why | ||||
|         await page.keyboard.up('Shift') | ||||
|         await page.keyboard.down('Shift') | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.mouse.click(line3.x, line3.y) | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.keyboard.up('Shift') | ||||
|         await page.waitForTimeout(100) | ||||
|         await page | ||||
|           .getByRole('button', { | ||||
|             name: 'Length: open menu', | ||||
| @ -194,6 +231,7 @@ part002 = startSketchOn('XZ') | ||||
|           `offset = ${offset},` | ||||
|         ) | ||||
|  | ||||
|         await pollEditorLinesSelectedLength(page, 2) | ||||
|         const activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|         await expect(activeLinesContent[0]).toHaveText( | ||||
|           `|> line([74.36, 130.4], %, $seg01)` | ||||
| @ -229,33 +267,37 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { testName, value, constraint } of cases) { | ||||
|       test(`${constraint} - ${testName}`, async ({ page }) => { | ||||
|       test(`${constraint} - ${testName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
|   |> line([51.19, 48.97], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|         |> line([51.19, 48.97], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const [line1, line3] = await Promise.all([ | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), | ||||
| @ -335,33 +377,37 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { testName, addVariable, value, constraint } of cases) { | ||||
|       test(`${constraint} - ${testName}`, async ({ page }) => { | ||||
|       test(`${constraint} - ${testName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
|   |> line([51.19, 48.97], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|         |> line([51.19, 48.97], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const [line3] = await Promise.all([ | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), | ||||
|         ]) | ||||
| @ -372,9 +418,11 @@ part002 = startSketchOn('XZ') | ||||
|           await page.mouse.click(900, 250) | ||||
|         } | ||||
|         await page.keyboard.down('Shift') | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.mouse.click(line3.x, line3.y) | ||||
|         await page.waitForTimeout(100) // this wait is needed for webkit - not sure why | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.keyboard.up('Shift') | ||||
|         await page.waitForTimeout(100) | ||||
|         await page | ||||
|           .getByRole('button', { | ||||
|             name: 'Length: open menu', | ||||
| @ -442,33 +490,37 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { testName, addVariable, value, axisSelect } of cases) { | ||||
|       test(`${testName}`, async ({ page }) => { | ||||
|       test(`${testName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
|   |> line([51.19, 48.97], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|         |> line([51.19, 48.97], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const [line1, line3] = await Promise.all([ | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), | ||||
|           u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), | ||||
| @ -552,33 +604,37 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { testName, addVariable, value, constraint } of cases) { | ||||
|       test(`${testName}`, async ({ page }) => { | ||||
|       test(`${testName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
|   |> line([51.19, 48.97], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|         |> line([51.19, 48.97], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const line3 = await u.getSegmentBodyCoords( | ||||
|           `[data-overlay-index="${2}"]` | ||||
|         ) | ||||
| @ -628,33 +684,37 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { codeAfter, constraintName } of cases) { | ||||
|       test(`${constraintName}`, async ({ page }) => { | ||||
|       test(`${constraintName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async (customCode) => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
|   |> line([51.19, 48.97], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|         |> line([51.19, 48.97], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const line1 = await u.getSegmentBodyCoords( | ||||
|           `[data-overlay-index="${0}"]` | ||||
|         ) | ||||
| @ -673,8 +733,8 @@ part002 = startSketchOn('XZ') | ||||
|         await page.keyboard.up('Shift') | ||||
|  | ||||
|         // check actives lines | ||||
|         await pollEditorLinesSelectedLength(page, codeAfter.length) | ||||
|         const activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|         await expect(activeLinesContent).toHaveLength(codeAfter.length) | ||||
|  | ||||
|         const constraintMenuButton = page.getByRole('button', { | ||||
|           name: 'Length: open menu', | ||||
| @ -725,32 +785,36 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { codeAfter, constraintName } of cases) { | ||||
|       test(`${constraintName}`, async ({ page }) => { | ||||
|       test(`${constraintName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const line1 = await u.getBoundingBox(`[data-overlay-index="${0}"]`) | ||||
|         const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`) | ||||
|  | ||||
| @ -777,8 +841,8 @@ part002 = startSketchOn('XZ') | ||||
|         // check there are still 2 cursors (they should stay on the same lines as before constraint was applied) | ||||
|         await expect(page.locator('.cm-cursor')).toHaveCount(2) | ||||
|         // check actives lines | ||||
|         await pollEditorLinesSelectedLength(page, 2) | ||||
|         const activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|         await expect(activeLinesContent).toHaveLength(2) | ||||
|  | ||||
|         // check both cursors are where they should be after constraint is applied | ||||
|         await expect(activeLinesContent[0]).toHaveText( | ||||
| @ -802,40 +866,47 @@ part002 = startSketchOn('XZ') | ||||
|       }, | ||||
|     ] as const | ||||
|     for (const { codeAfter, constraintName, axisClick } of cases) { | ||||
|       test(`${constraintName}`, async ({ page }) => { | ||||
|       test(`${constraintName}`, async ({ page, homePage }) => { | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
|             'persistCode', | ||||
|             `yo = 5 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> line([74.36, 130.4], %) | ||||
|   |> line([78.92, -120.11], %) | ||||
|   |> line([9.16, 77.79], %) | ||||
| part002 = startSketchOn('XZ') | ||||
|   |> startProfileAt([299.05, 231.45], %) | ||||
|   |> xLine(-425.34, %, $seg_what) | ||||
|   |> yLine(-264.06, %) | ||||
|   |> xLine(segLen(seg_what), %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|       part001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-7.54, -26.74], %) | ||||
|         |> line([74.36, 130.4], %) | ||||
|         |> line([78.92, -120.11], %) | ||||
|         |> line([9.16, 77.79], %) | ||||
|       part002 = startSketchOn('XZ') | ||||
|         |> startProfileAt([299.05, 231.45], %) | ||||
|         |> xLine(-425.34, %, $seg_what) | ||||
|         |> yLine(-264.06, %) | ||||
|         |> xLine(segLen(seg_what), %) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %)` | ||||
|           ) | ||||
|         }) | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|  | ||||
|         await page.getByText('line([74.36, 130.4], %)').click() | ||||
|         await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|         // Wait for overlays to populate | ||||
|         await page.waitForTimeout(1000) | ||||
|  | ||||
|         const line3 = await u.getBoundingBox(`[data-overlay-index="${2}"]`) | ||||
|  | ||||
|         // select segment and axis by holding down shift | ||||
|         await page.mouse.click(line3.x - 3, line3.y + 20) | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.keyboard.down('Shift') | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.mouse.click(axisClick.x, axisClick.y) | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.keyboard.up('Shift') | ||||
|         await page.waitForTimeout(100) | ||||
|         const constraintMenuButton = page.getByRole('button', { | ||||
|           name: 'Length: open menu', | ||||
|         }) | ||||
| @ -857,21 +928,23 @@ part002 = startSketchOn('XZ') | ||||
|  | ||||
|   test('Horizontally constrained line remains selected after applying constraint', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     test.setTimeout(70_000) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-1.05, -1.07], %) | ||||
|   |> line([3.79, 2.68], %, $seg01) | ||||
|   |> line([3.13, -2.4], %)` | ||||
|     |> startProfileAt([-1.05, -1.07], %) | ||||
|     |> line([3.79, 2.68], %, $seg01) | ||||
|     |> line([3.13, -2.4], %)` | ||||
|       ) | ||||
|     }) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await page.getByText('line([3.79, 2.68], %, $seg01)').click() | ||||
|     await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled( | ||||
| @ -879,6 +952,9 @@ part002 = startSketchOn('XZ') | ||||
|     ) | ||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|  | ||||
|     // Wait for overlays to populate | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     await page.waitForTimeout(100) | ||||
|     const lineBefore = await u.getSegmentBodyCoords( | ||||
|       `[data-overlay-index="1"]`, | ||||
| @ -899,11 +975,17 @@ part002 = startSketchOn('XZ') | ||||
|         name: 'Length: open menu', | ||||
|       }) | ||||
|       .click() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.getByRole('button', { name: 'Horizontal', exact: true }).click() | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     await pollEditorLinesSelectedLength(page, 1) | ||||
|     let activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|     await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`) | ||||
|  | ||||
|     // Wait for code editor to settle. | ||||
|     await page.waitForTimeout(2000) | ||||
|  | ||||
|     // If the overlay-angle is updated the THREE.js scene is in a good state | ||||
|     await expect( | ||||
|       await page.locator('[data-overlay-index="1"]') | ||||
| @ -913,11 +995,17 @@ part002 = startSketchOn('XZ') | ||||
|       `[data-overlay-index="1"]`, | ||||
|       0 | ||||
|     ) | ||||
|     expect( | ||||
|       await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE) | ||||
|     ).toBeLessThan(3) | ||||
|  | ||||
|     await page.waitForTimeout(300) | ||||
|     const linebb = await u.getBoundingBox('[data-overlay-index="1"]') | ||||
|     await page.mouse.move(linebb.x, linebb.y, { steps: 25 }) | ||||
|     await page.mouse.click(linebb.x, linebb.y) | ||||
|  | ||||
|     await expect | ||||
|       .poll(async () => await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE)) | ||||
|       .toBeLessThan(3) | ||||
|  | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     await page | ||||
|       .getByRole('button', { | ||||
|         name: 'Length: open menu', | ||||
| @ -931,6 +1019,7 @@ part002 = startSketchOn('XZ') | ||||
|     await page.getByLabel('length Value').fill('10') | ||||
|     await page.getByRole('button', { name: 'Add constraining value' }).click() | ||||
|  | ||||
|     await pollEditorLinesSelectedLength(page, 1) | ||||
|     activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|     await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`) | ||||
|  | ||||
|  | ||||
| @ -1,18 +1,9 @@ | ||||
| import { _test, _expect } from './playwright-deprecated' | ||||
| import { test } from './fixtures/fixtureSetup' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { test, expect } from './zoo-test' | ||||
| import { getUtils } from './test-utils' | ||||
| import { uuidv4 } from 'lib/utils' | ||||
| import { TEST_CODE_GIZMO } from './storageStates' | ||||
|  | ||||
| _test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| _test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| _test.describe('Testing Gizmo', () => { | ||||
| test.describe('Testing Gizmo', () => { | ||||
|   const cases = [ | ||||
|     { | ||||
|       testDescription: 'top view', | ||||
| @ -57,14 +48,17 @@ _test.describe('Testing Gizmo', () => { | ||||
|     expectedCameraTarget, | ||||
|     testDescription, | ||||
|   } of cases) { | ||||
|     _test(`check ${testDescription}`, async ({ page, browserName }) => { | ||||
|     test(`check ${testDescription}`, async ({ page, homePage }) => { | ||||
|       const u = await getUtils(page) | ||||
|       await page.addInitScript((TEST_CODE_GIZMO) => { | ||||
|         localStorage.setItem('persistCode', TEST_CODE_GIZMO) | ||||
|       }, TEST_CODE_GIZMO) | ||||
|       await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.waitForPageLoad() | ||||
|  | ||||
|       await page.waitForTimeout(100) | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -117,30 +111,30 @@ _test.describe('Testing Gizmo', () => { | ||||
|  | ||||
|       await Promise.all([ | ||||
|         // position | ||||
|         _expect(page.getByTestId('cam-x-position')).toHaveValue( | ||||
|         expect(page.getByTestId('cam-x-position')).toHaveValue( | ||||
|           expectedCameraPosition.x.toString() | ||||
|         ), | ||||
|         _expect(page.getByTestId('cam-y-position')).toHaveValue( | ||||
|         expect(page.getByTestId('cam-y-position')).toHaveValue( | ||||
|           expectedCameraPosition.y.toString() | ||||
|         ), | ||||
|         _expect(page.getByTestId('cam-z-position')).toHaveValue( | ||||
|         expect(page.getByTestId('cam-z-position')).toHaveValue( | ||||
|           expectedCameraPosition.z.toString() | ||||
|         ), | ||||
|         // target | ||||
|         _expect(page.getByTestId('cam-x-target')).toHaveValue( | ||||
|         expect(page.getByTestId('cam-x-target')).toHaveValue( | ||||
|           expectedCameraTarget.x.toString() | ||||
|         ), | ||||
|         _expect(page.getByTestId('cam-y-target')).toHaveValue( | ||||
|         expect(page.getByTestId('cam-y-target')).toHaveValue( | ||||
|           expectedCameraTarget.y.toString() | ||||
|         ), | ||||
|         _expect(page.getByTestId('cam-z-target')).toHaveValue( | ||||
|         expect(page.getByTestId('cam-z-target')).toHaveValue( | ||||
|           expectedCameraTarget.z.toString() | ||||
|         ), | ||||
|       ]) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   _test('Context menu and popover menu', async ({ page }) => { | ||||
|   test('Context menu and popover menu', async ({ page, homePage }) => { | ||||
|     const testCase = { | ||||
|       testDescription: 'Right view', | ||||
|       expectedCameraPosition: { x: 5660.02, y: -152, z: 26 }, | ||||
| @ -152,9 +146,9 @@ _test.describe('Testing Gizmo', () => { | ||||
|     await page.addInitScript((TEST_CODE_GIZMO) => { | ||||
|       localStorage.setItem('persistCode', TEST_CODE_GIZMO) | ||||
|     }, TEST_CODE_GIZMO) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await page.waitForTimeout(100) | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -196,7 +190,7 @@ _test.describe('Testing Gizmo', () => { | ||||
|     const buttonToTest = page.getByRole('button', { | ||||
|       name: testCase.testDescription, | ||||
|     }) | ||||
|     await _expect(buttonToTest).toBeVisible() | ||||
|     await expect(buttonToTest).toBeVisible() | ||||
|     await buttonToTest.click() | ||||
|  | ||||
|     // Now assert we've moved to the correct view | ||||
| @ -215,23 +209,23 @@ _test.describe('Testing Gizmo', () => { | ||||
|  | ||||
|     await Promise.all([ | ||||
|       // position | ||||
|       _expect(page.getByTestId('cam-x-position')).toHaveValue( | ||||
|       expect(page.getByTestId('cam-x-position')).toHaveValue( | ||||
|         testCase.expectedCameraPosition.x.toString() | ||||
|       ), | ||||
|       _expect(page.getByTestId('cam-y-position')).toHaveValue( | ||||
|       expect(page.getByTestId('cam-y-position')).toHaveValue( | ||||
|         testCase.expectedCameraPosition.y.toString() | ||||
|       ), | ||||
|       _expect(page.getByTestId('cam-z-position')).toHaveValue( | ||||
|       expect(page.getByTestId('cam-z-position')).toHaveValue( | ||||
|         testCase.expectedCameraPosition.z.toString() | ||||
|       ), | ||||
|       // target | ||||
|       _expect(page.getByTestId('cam-x-target')).toHaveValue( | ||||
|       expect(page.getByTestId('cam-x-target')).toHaveValue( | ||||
|         testCase.expectedCameraTarget.x.toString() | ||||
|       ), | ||||
|       _expect(page.getByTestId('cam-y-target')).toHaveValue( | ||||
|       expect(page.getByTestId('cam-y-target')).toHaveValue( | ||||
|         testCase.expectedCameraTarget.y.toString() | ||||
|       ), | ||||
|       _expect(page.getByTestId('cam-z-target')).toHaveValue( | ||||
|       expect(page.getByTestId('cam-z-target')).toHaveValue( | ||||
|         testCase.expectedCameraTarget.z.toString() | ||||
|       ), | ||||
|     ]) | ||||
| @ -242,32 +236,59 @@ _test.describe('Testing Gizmo', () => { | ||||
|     const gizmoPopoverButton = page.getByRole('button', { | ||||
|       name: 'view settings', | ||||
|     }) | ||||
|     await _expect(gizmoPopoverButton).toBeVisible() | ||||
|     await expect(gizmoPopoverButton).toBeVisible() | ||||
|     await gizmoPopoverButton.click() | ||||
|     await _expect(buttonToTest).toBeVisible() | ||||
|     await expect(buttonToTest).toBeVisible() | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test.describe(`Testing gizmo, fixture-based`, () => { | ||||
|   test('Center on selection from menu', async ({ | ||||
|     app, | ||||
|     context, | ||||
|     page, | ||||
|     homePage, | ||||
|     cmdBar, | ||||
|     editor, | ||||
|     toolbar, | ||||
|     scene, | ||||
|   }) => { | ||||
|     test.skip( | ||||
|       process.platform === 'win32', | ||||
|       'Fails on windows in CI, can not be replicated locally on windows.' | ||||
|     ) | ||||
|     await context.addInitScript(() => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         ` | ||||
|         const sketch002 = startSketchOn('XZ') | ||||
|           |> startProfileAt([-108.83, -57.48], %) | ||||
|           |> angledLine([0, 105.13], %, $rectangleSegmentA001) | ||||
|           |> angledLine([ | ||||
|                segAng(rectangleSegmentA001) - 90, | ||||
|                77.9 | ||||
|              ], %) | ||||
|           |> angledLine([ | ||||
|                segAng(rectangleSegmentA001), | ||||
|                -segLen(rectangleSegmentA001) | ||||
|              ], %) | ||||
|           |> close(%) | ||||
|         const sketch001 = startSketchOn('XZ') | ||||
|           |> circle({ | ||||
|                center: [818.33, 168.1], | ||||
|                radius: 182.8 | ||||
|              }, %) | ||||
|           |> extrude(50, %) | ||||
|       ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     const u = await getUtils(page) | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await test.step(`Setup`, async () => { | ||||
|       const file = await app.getInputFile('test-circle-extrude.kcl') | ||||
|       await app.initialise(file) | ||||
|       await scene.expectState({ | ||||
|         camera: { | ||||
|           position: [4982.21, -23865.37, 13810.64], | ||||
|           target: [4982.21, 0, 2737.1], | ||||
|           position: [11912.6, -39586.98, 21391.21], | ||||
|           target: [11912.6, -635, 3317.49], | ||||
|         }, | ||||
|       }) | ||||
|     }) | ||||
| @ -275,7 +296,7 @@ test.describe(`Testing gizmo, fixture-based`, () => { | ||||
|  | ||||
|     await test.step(`Select an edge of this circle`, async () => { | ||||
|       const circleSnippet = | ||||
|         'circle({ center = [318.33, 168.1], radius = 182.8 }, %)' | ||||
|         'circle({ center: [818.33, 168.1], radius: 182.8 }, %)' | ||||
|       await moveToCircle() | ||||
|       await clickCircle() | ||||
|       await editor.expectState({ | ||||
| @ -292,8 +313,8 @@ test.describe(`Testing gizmo, fixture-based`, () => { | ||||
|     await test.step(`Verify the camera moved`, async () => { | ||||
|       await scene.expectState({ | ||||
|         camera: { | ||||
|           position: [0, -23865.37, 11073.53], | ||||
|           target: [0, 0, 0], | ||||
|           position: [20785.58, -40221.98, 22343.46], | ||||
|           target: [20785.58, -1270, 4269.74], | ||||
|         }, | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
| @ -1,18 +1,8 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates' | ||||
| import * as TOML from '@iarna/toml' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
| import { test, expect } from './zoo-test' | ||||
| import { getUtils } from './test-utils' | ||||
|  | ||||
| test.describe('Test toggling perspective', () => { | ||||
|   test('via command palette and toggle', async ({ page }) => { | ||||
|   test.fixme('via command palette and toggle', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Locators and constants | ||||
| @ -20,7 +10,7 @@ test.describe('Test toggling perspective', () => { | ||||
|     const screenHeight = 500 | ||||
|     const checkedScreenLocation = { | ||||
|       x: screenWidth * 0.71, | ||||
|       y: screenHeight * 0.4, | ||||
|       y: screenHeight * 0.2, | ||||
|     } | ||||
|     const backgroundColor: [number, number, number] = [29, 29, 29] | ||||
|     const xzPlaneColor: [number, number, number] = [82, 55, 96] | ||||
| @ -40,8 +30,8 @@ test.describe('Test toggling perspective', () => { | ||||
|     }) | ||||
|  | ||||
|     await test.step('Setup', async () => { | ||||
|       await page.setViewportSize({ width: screenWidth, height: screenHeight }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: screenWidth, height: screenHeight }) | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.closeKclCodePanel() | ||||
|       await expect | ||||
|         .poll(async () => locationToHaveColor(backgroundColor), { | ||||
| @ -52,11 +42,17 @@ test.describe('Test toggling perspective', () => { | ||||
|       await expect(projectionToggle).toHaveAttribute('aria-checked', 'true') | ||||
|     }) | ||||
|  | ||||
|     // Extremely wild note: flicking between ortho and persp actually changes | ||||
|     // the orientation of the axis/camera. How can you see this? Well toggle it, | ||||
|     // then refresh. You'll see it doesn't match what we left. | ||||
|     await test.step('Switch to ortho via command palette', async () => { | ||||
|       await commandPaletteButton.click() | ||||
|       await page.waitForTimeout(1000) | ||||
|       await commandOption.click() | ||||
|       await page.waitForTimeout(1000) | ||||
|       await orthoOption.click() | ||||
|       await expect(commandToast).toBeVisible() | ||||
|       await expect(commandToast).not.toBeVisible() | ||||
|       await expect | ||||
|         .poll(async () => locationToHaveColor(xzPlaneColor), { | ||||
|           timeout: 5000, | ||||
| @ -67,27 +63,9 @@ test.describe('Test toggling perspective', () => { | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Refresh the page and ensure the stream is loaded in ortho`, async () => { | ||||
|       // In playwright web, the settings set while testing are not persisted because | ||||
|       // the `addInitScript` within `setup` is re-run on page reload | ||||
|       await page.addInitScript( | ||||
|         ({ settingsKey, settings }) => { | ||||
|           localStorage.setItem(settingsKey, settings) | ||||
|         }, | ||||
|         { | ||||
|           settingsKey: TEST_SETTINGS_KEY, | ||||
|           settings: TOML.stringify({ | ||||
|             settings: { | ||||
|               ...TEST_SETTINGS, | ||||
|               modeling: { | ||||
|                 ...TEST_SETTINGS.modeling, | ||||
|                 cameraProjection: 'orthographic', | ||||
|               }, | ||||
|             }, | ||||
|           }), | ||||
|         } | ||||
|       ) | ||||
|       await page.reload() | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.waitForTimeout(1000) | ||||
|       await u.closeKclCodePanel() | ||||
|       await expect | ||||
|         .poll(async () => locationToHaveColor(xzPlaneColor), { | ||||
|           timeout: 5000, | ||||
|  | ||||
| @ -1,35 +1,30 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { getUtils, setup, setupElectron, tearDown } from './test-utils' | ||||
| import { test, expect } from './zoo-test' | ||||
| import { getUtils } from './test-utils' | ||||
| import { bracket } from 'lib/exampleKcl' | ||||
| import * as fsp from 'fs/promises' | ||||
| import { join } from 'path' | ||||
| import { FILE_EXT } from 'lib/constants' | ||||
| import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.describe('Testing in-app sample loading', () => { | ||||
|   /** | ||||
|    * Note this test implicitly depends on the KCL sample "car-wheel.kcl", | ||||
|    * its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl | ||||
|    */ | ||||
|   test('Web: should overwrite current code, cannot create new file', async ({ | ||||
|     editor, | ||||
|     context, | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await test.step(`Test setup`, async () => { | ||||
|       await page.addInitScript((code) => { | ||||
|       await context.addInitScript((code) => { | ||||
|         window.localStorage.setItem('persistCode', code) | ||||
|       }, bracket) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|     }) | ||||
|  | ||||
|     // Locators and constants | ||||
| @ -54,13 +49,13 @@ test.describe('Testing in-app sample loading', () => { | ||||
|       }) | ||||
|     const warningText = page.getByText('Overwrite current file and units?') | ||||
|     const confirmButton = page.getByRole('button', { name: 'Submit command' }) | ||||
|     const codeLocator = page.locator('.cm-content') | ||||
|     const unitsToast = (unit: UnitLength_type) => | ||||
|       page.getByText(`Set default unit to "${unit}" for this project`) | ||||
|  | ||||
|     await test.step(`Precondition: check the initial code`, async () => { | ||||
|       await u.openKclCodePanel() | ||||
|       await expect(codeLocator).toContainText(bracket.split('\n')[0]) | ||||
|       await editor.scrollToText(bracket.split('\n')[0]) | ||||
|       await editor.expectEditor.toContain(bracket.split('\n')[0]) | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Load a KCL sample with the command palette`, async () => { | ||||
| @ -73,7 +68,7 @@ test.describe('Testing in-app sample loading', () => { | ||||
|       await expect(warningText).toBeVisible() | ||||
|       await confirmButton.click() | ||||
|  | ||||
|       await expect(codeLocator).toContainText('// ' + newSample.title) | ||||
|       await editor.expectEditor.toContain('// ' + newSample.title) | ||||
|       await expect(unitsToast('in')).toBeVisible() | ||||
|     }) | ||||
|   }) | ||||
| @ -86,16 +81,13 @@ test.describe('Testing in-app sample loading', () => { | ||||
|   test( | ||||
|     'Desktop: should create new file by default, optionally overwrite', | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName: _ }, testInfo) => { | ||||
|       const { electronApp, page, dir } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async (dir) => { | ||||
|           const bracketDir = join(dir, 'bracket') | ||||
|           await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|           await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, { | ||||
|             encoding: 'utf-8', | ||||
|           }) | ||||
|         }, | ||||
|     async ({ editor, context, page }, testInfo) => { | ||||
|       const { dir } = await context.folderSetupFn(async (dir) => { | ||||
|         const bracketDir = join(dir, 'bracket') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, { | ||||
|           encoding: 'utf-8', | ||||
|         }) | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
| @ -134,19 +126,19 @@ test.describe('Testing in-app sample loading', () => { | ||||
|         page.getByRole('listitem').filter({ | ||||
|           has: page.getByRole('button', { name }), | ||||
|         }) | ||||
|       const codeLocator = page.locator('.cm-content') | ||||
|       const unitsToast = (unit: UnitLength_type) => | ||||
|         page.getByText(`Set default unit to "${unit}" for this project`) | ||||
|  | ||||
|       await test.step(`Test setup`, async () => { | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|         await projectCard.click() | ||||
|         await u.waitForPageLoad() | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Precondition: check the initial code`, async () => { | ||||
|         await u.openKclCodePanel() | ||||
|         await expect(codeLocator).toContainText(bracket.split('\n')[0]) | ||||
|         await editor.scrollToText(bracket.split('\n')[0]) | ||||
|         await editor.expectEditor.toContain(bracket.split('\n')[0]) | ||||
|         await u.openFilePanel() | ||||
|  | ||||
|         await expect(projectMenuButton).toContainText('main.kcl') | ||||
| @ -163,7 +155,7 @@ test.describe('Testing in-app sample loading', () => { | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Ensure we made and opened a new file`, async () => { | ||||
|         await expect(codeLocator).toContainText('// ' + sampleOne.title) | ||||
|         await editor.expectEditor.toContain('// ' + sampleOne.title) | ||||
|         await expect(newlyCreatedFile(sampleOne.file)).toBeVisible() | ||||
|         await expect(projectMenuButton).toContainText(sampleOne.file) | ||||
|         await expect(unitsToast('in')).toBeVisible() | ||||
| @ -182,7 +174,7 @@ test.describe('Testing in-app sample loading', () => { | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Ensure we overwrote the current file without navigating`, async () => { | ||||
|         await expect(codeLocator).toContainText('// ' + sampleTwo.title) | ||||
|         await editor.expectEditor.toContain('// ' + sampleTwo.title) | ||||
|         await test.step(`Check actual file contents`, async () => { | ||||
|           await expect | ||||
|             .poll(async () => { | ||||
| @ -198,8 +190,6 @@ test.describe('Testing in-app sample loading', () => { | ||||
|         await expect(projectMenuButton).toContainText(sampleOne.file) | ||||
|         await expect(unitsToast('mm')).toBeVisible() | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| @ -1,16 +1,9 @@ | ||||
| import { test, expect, Page } from '@playwright/test' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
|  | ||||
| import { deg, getUtils, setup, tearDown, wiggleMove } from './test-utils' | ||||
| import { deg, getUtils, wiggleMove } from './test-utils' | ||||
| import { LineInputsType } from 'lang/std/sketchcombos' | ||||
| 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) | ||||
| }) | ||||
| import { EditorFixture } from './fixtures/editorFixture' | ||||
|  | ||||
| test.describe('Testing segment overlays', () => { | ||||
|   test.describe('Hover over a segment should show its overlay, hovering over the input overlays should show its popover, clicking the input overlay should constrain/unconstrain it:\nfor the following segments', () => { | ||||
| @ -24,7 +17,7 @@ test.describe('Testing segment overlays', () => { | ||||
|      * @param {number} options.steps - The number of steps to perform | ||||
|      */ | ||||
|     const _clickConstrained = | ||||
|       (page: Page) => | ||||
|       (page: Page, editor: EditorFixture) => | ||||
|       async ({ | ||||
|         hoverPos, | ||||
|         constraintType, | ||||
| @ -58,10 +51,11 @@ test.describe('Testing segment overlays', () => { | ||||
|         y = hoverPos.y - Math.sin(ang * deg) * 32 | ||||
|         await page.mouse.move(x, y) | ||||
|         await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator) | ||||
|         await page.mouse.move(x, y) | ||||
|  | ||||
|         await expect(page.locator('.cm-content')).toContainText( | ||||
|           expectBeforeUnconstrained | ||||
|         ) | ||||
|         await editor.expectEditor.toContain(expectBeforeUnconstrained, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|         const constrainedLocator = page.locator( | ||||
|           `[data-constraint-type="${constraintType}"][data-is-constrained="true"]` | ||||
|         ) | ||||
| @ -71,9 +65,9 @@ test.describe('Testing segment overlays', () => { | ||||
|           await page.getByTestId('constraint-symbol-popover').count() | ||||
|         ).toBeGreaterThan(0) | ||||
|         await constrainedLocator.click() | ||||
|         await expect(page.locator('.cm-content')).toContainText( | ||||
|           expectAfterUnconstrained | ||||
|         ) | ||||
|         await editor.expectEditor.toContain(expectAfterUnconstrained, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|  | ||||
|         await page.mouse.move(0, 0) | ||||
|         await page.waitForTimeout(1000) | ||||
| @ -81,6 +75,7 @@ test.describe('Testing segment overlays', () => { | ||||
|         y = hoverPos.y - Math.sin(ang * deg) * 32 | ||||
|         await page.mouse.move(x, y) | ||||
|         await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator) | ||||
|         await page.mouse.move(x, y) | ||||
|  | ||||
|         const unconstrainedLocator = page.locator( | ||||
|           `[data-constraint-type="${constraintType}"][data-is-constrained="false"]` | ||||
| @ -92,7 +87,9 @@ test.describe('Testing segment overlays', () => { | ||||
|         ).toBeGreaterThan(0) | ||||
|         await unconstrainedLocator.click() | ||||
|         await page.getByText('Add variable').click() | ||||
|         await expect(page.locator('.cm-content')).toContainText(expectFinal) | ||||
|         await editor.expectEditor.toContain(expectFinal, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|       } | ||||
|  | ||||
|     /** | ||||
| @ -105,7 +102,7 @@ test.describe('Testing segment overlays', () => { | ||||
|      * @param {number} options.steps - The number of steps to perform | ||||
|      */ | ||||
|     const _clickUnconstrained = | ||||
|       (page: Page) => | ||||
|       (page: Page, editor: EditorFixture) => | ||||
|       async ({ | ||||
|         hoverPos, | ||||
|         constraintType, | ||||
| @ -137,11 +134,12 @@ test.describe('Testing segment overlays', () => { | ||||
|         y = hoverPos.y - Math.sin(ang * deg) * 32 | ||||
|         await page.mouse.move(x, y) | ||||
|         await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator) | ||||
|         await page.mouse.move(x, y) | ||||
|  | ||||
|         await expect(page.getByText('Added variable')).not.toBeVisible() | ||||
|         await expect(page.locator('.cm-content')).toContainText( | ||||
|           expectBeforeUnconstrained | ||||
|         ) | ||||
|         await editor.expectEditor.toContain(expectBeforeUnconstrained, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|         const unconstrainedLocator = page.locator( | ||||
|           `[data-constraint-type="${constraintType}"][data-is-constrained="false"]` | ||||
|         ) | ||||
| @ -152,9 +150,9 @@ test.describe('Testing segment overlays', () => { | ||||
|         ).toBeGreaterThan(0) | ||||
|         await unconstrainedLocator.click() | ||||
|         await page.getByText('Add variable').click() | ||||
|         await expect(page.locator('.cm-content')).toContainText( | ||||
|           expectAfterUnconstrained | ||||
|         ) | ||||
|         await editor.expectEditor.toContain(expectAfterUnconstrained, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|         await expect(page.getByText('Added variable')).not.toBeVisible() | ||||
|  | ||||
|         await page.mouse.move(0, 0) | ||||
| @ -163,6 +161,7 @@ test.describe('Testing segment overlays', () => { | ||||
|         y = hoverPos.y - Math.sin(ang * deg) * 32 | ||||
|         await page.mouse.move(x, y) | ||||
|         await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator) | ||||
|         await page.mouse.move(x, y) | ||||
|  | ||||
|         const constrainedLocator = page.locator( | ||||
|           `[data-constraint-type="${constraintType}"][data-is-constrained="true"]` | ||||
| @ -173,41 +172,45 @@ test.describe('Testing segment overlays', () => { | ||||
|           await page.getByTestId('constraint-symbol-popover').count() | ||||
|         ).toBeGreaterThan(0) | ||||
|         await constrainedLocator.click() | ||||
|         await expect(page.locator('.cm-content')).toContainText(expectFinal) | ||||
|         await editor.expectEditor.toContain(expectFinal, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|       } | ||||
|     test.setTimeout(120000) | ||||
|     test('for segments [line, angledLine, lineTo, xLineTo]', async ({ | ||||
|       page, | ||||
|       editor, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([5 + 0, 20 + 0], %) | ||||
|     |> line([0.5, -14 + 0], %) | ||||
|     |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|     |> lineTo([5 + 33, 20 + 11.5 + 0], %) | ||||
|     |> xLineTo(5 + 9 - 5, %) | ||||
|     |> yLineTo(20 + -10.77, %, $a) | ||||
|     |> xLine(26.04, %) | ||||
|     |> yLine(21.14 + 0, %) | ||||
|     |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|     |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|     |> angledLineToX({ angle = 3 + 0, to = 5 + 26 }, %) | ||||
|     |> angledLineToY({ angle = 89, to = 20 + 9.14 + 0 }, %) | ||||
|     |> angledLineThatIntersects({ | ||||
|           angle = 4.14, | ||||
|           intersectTag = a, | ||||
|           offset = 9 | ||||
|         }, %) | ||||
|     |> tangentialArcTo([5 + 3.14 + 13, 20 + 3.14], %) | ||||
|         ` | ||||
|         |> startProfileAt([5 + 0, 20 + 0], %) | ||||
|         |> line([0.5, -14 + 0], %) | ||||
|         |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|         |> lineTo([5 + 33, 20 + 11.5 + 0], %) | ||||
|         |> xLineTo(5 + 9 - 5, %) | ||||
|         |> yLineTo(20 + -10.77, %, $a) | ||||
|         |> xLine(26.04, %) | ||||
|         |> yLine(21.14 + 0, %) | ||||
|         |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|         |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|         |> angledLineToX({ angle = 3 + 0, to = 5 + 26 }, %) | ||||
|         |> angledLineToY({ angle = 89, to = 20 + 9.14 + 0 }, %) | ||||
|         |> angledLineThatIntersects({ | ||||
|         angle = 4.14, | ||||
|         intersectTag = a, | ||||
|         offset = 9 | ||||
|       }, %) | ||||
|         |> tangentialArcTo([5 + 3.14 + 13, 20 + 3.14], %) | ||||
|       ` | ||||
|         ) | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -221,8 +224,8 @@ test.describe('Testing segment overlays', () => { | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(13) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page) | ||||
|       const clickConstrained = _clickConstrained(page) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|       const clickConstrained = _clickConstrained(page, editor) | ||||
|  | ||||
|       await u.openAndClearDebugPanel() | ||||
|       await u.sendCustomCmd({ | ||||
| @ -340,34 +343,38 @@ test.describe('Testing segment overlays', () => { | ||||
|         locator: '[data-overlay-toolbar-index="3"]', | ||||
|       }) | ||||
|     }) | ||||
|     test('for segments [yLineTo, xLine]', async ({ page }) => { | ||||
|     test('for segments [yLineTo, xLine]', async ({ | ||||
|       page, | ||||
|       editor, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `yRel001 = -14 | ||||
| xRel001 = 0.5 | ||||
| angle001 = 3 | ||||
| len001 = 32 | ||||
| yAbs001 = 11.5 | ||||
| xAbs001 = 33 | ||||
| xAbs002 = 4 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([0.5, yRel001], %) | ||||
|   |> angledLine({ angle = angle001, length = len001 }, %) | ||||
|   |> lineTo([33, yAbs001], %) | ||||
|   |> xLineTo(xAbs002, %) | ||||
|   |> yLineTo(-10.77, %, $a) | ||||
|   |> xLine(26.04, %) | ||||
|   |> yLine(21.14 + 0, %) | ||||
|   |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|         ` | ||||
|     xRel001 = 0.5 | ||||
|     angle001 = 3 | ||||
|     len001 = 32 | ||||
|     yAbs001 = 11.5 | ||||
|     xAbs001 = 33 | ||||
|     xAbs002 = 4 | ||||
|     part001 = startSketchOn('XZ') | ||||
|       |> startProfileAt([0, 0], %) | ||||
|       |> line([0.5, yRel001], %) | ||||
|       |> angledLine({ angle = angle001, length = len001 }, %) | ||||
|       |> lineTo([33, yAbs001], %) | ||||
|       |> xLineTo(xAbs002, %) | ||||
|       |> yLineTo(-10.77, %, $a) | ||||
|       |> xLine(26.04, %) | ||||
|       |> yLine(21.14 + 0, %) | ||||
|       |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|       ` | ||||
|         ) | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -381,7 +388,7 @@ part001 = startSketchOn('XZ') | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(8) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|  | ||||
|       await page.mouse.move(700, 250) | ||||
|       await page.waitForTimeout(100) | ||||
| @ -417,37 +424,39 @@ part001 = startSketchOn('XZ') | ||||
|     }) | ||||
|     test('for segments [yLine, angledLineOfXLength, angledLineOfYLength]', async ({ | ||||
|       page, | ||||
|       editor, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([0.5, -14 + 0], %) | ||||
|     |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|     |> lineTo([33, 11.5 + 0], %) | ||||
|     |> xLineTo(9 - 5, %) | ||||
|     |> yLineTo(-10.77, %, $a) | ||||
|     |> xLine(26.04, %) | ||||
|     |> yLine(21.14 + 0, %) | ||||
|     |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|     |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|     |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|     |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|     |> angledLineThatIntersects({ | ||||
|           angle = 4.14, | ||||
|           intersectTag = a, | ||||
|           offset = 9 | ||||
|         }, %) | ||||
|     |> tangentialArcTo([3.14 + 13, 3.14], %) | ||||
|         ` | ||||
|         |> startProfileAt([0, 0], %) | ||||
|         |> line([0.5, -14 + 0], %) | ||||
|         |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|         |> lineTo([33, 11.5 + 0], %) | ||||
|         |> xLineTo(9 - 5, %) | ||||
|         |> yLineTo(-10.77, %, $a) | ||||
|         |> xLine(26.04, %) | ||||
|         |> yLine(21.14 + 0, %) | ||||
|         |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|         |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|         |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|         |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|         |> angledLineThatIntersects({ | ||||
|         angle = 4.14, | ||||
|         intersectTag = a, | ||||
|         offset = 9 | ||||
|       }, %) | ||||
|         |> tangentialArcTo([3.14 + 13, 3.14], %) | ||||
|       ` | ||||
|         ) | ||||
|         localStorage.setItem('disableAxis', 'true') | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -462,8 +471,8 @@ part001 = startSketchOn('XZ') | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(13) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page) | ||||
|       const clickConstrained = _clickConstrained(page) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|       const clickConstrained = _clickConstrained(page, editor) | ||||
|  | ||||
|       let ang = 0 | ||||
|  | ||||
| @ -546,37 +555,39 @@ part001 = startSketchOn('XZ') | ||||
|     }) | ||||
|     test('for segments [angledLineToX, angledLineToY, angledLineThatIntersects]', async ({ | ||||
|       page, | ||||
|       editor, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([0.5, -14 + 0], %) | ||||
|     |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|     |> lineTo([33, 11.5 + 0], %) | ||||
|     |> xLineTo(9 - 5, %) | ||||
|     |> yLineTo(-10.77, %, $a) | ||||
|     |> xLine(26.04, %) | ||||
|     |> yLine(21.14 + 0, %) | ||||
|     |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|     |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|     |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|     |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|     |> angledLineThatIntersects({ | ||||
|           angle = 4.14, | ||||
|           intersectTag = a, | ||||
|           offset = 9 | ||||
|         }, %) | ||||
|     |> tangentialArcTo([3.14 + 13, 1.14], %) | ||||
|         ` | ||||
|         |> startProfileAt([0, 0], %) | ||||
|         |> line([0.5, -14 + 0], %) | ||||
|         |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|         |> lineTo([33, 11.5 + 0], %) | ||||
|         |> xLineTo(9 - 5, %) | ||||
|         |> yLineTo(-10.77, %, $a) | ||||
|         |> xLine(26.04, %) | ||||
|         |> yLine(21.14 + 0, %) | ||||
|         |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|         |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|         |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|         |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|         |> angledLineThatIntersects({ | ||||
|         angle = 4.14, | ||||
|         intersectTag = a, | ||||
|         offset = 9 | ||||
|       }, %) | ||||
|         |> tangentialArcTo([3.14 + 13, 1.14], %) | ||||
|       ` | ||||
|         ) | ||||
|         localStorage.setItem('disableAxis', 'true') | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -590,8 +601,8 @@ part001 = startSketchOn('XZ') | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(13) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page) | ||||
|       const clickConstrained = _clickConstrained(page) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|       const clickConstrained = _clickConstrained(page, editor) | ||||
|  | ||||
|       let ang = 0 | ||||
|  | ||||
| @ -660,20 +671,20 @@ part001 = startSketchOn('XZ') | ||||
|         }, | ||||
|         constraintType: 'angle', | ||||
|         expectBeforeUnconstrained: `angledLineThatIntersects({ | ||||
|       angle = 4.14, | ||||
|       intersectTag = a, | ||||
|       offset = 9 | ||||
|     }, %)`, | ||||
|     angle = 4.14, | ||||
|     intersectTag = a, | ||||
|     offset = 9 | ||||
|         }, %)`, | ||||
|         expectAfterUnconstrained: `angledLineThatIntersects({ | ||||
|       angle = angle003, | ||||
|       intersectTag = a, | ||||
|       offset = 9 | ||||
|     }, %)`, | ||||
|     angle = angle003, | ||||
|     intersectTag = a, | ||||
|     offset = 9 | ||||
|         }, %)`, | ||||
|         expectFinal: `angledLineThatIntersects({ | ||||
|       angle = -176, | ||||
|       offset = 9, | ||||
|       intersectTag = a | ||||
|     }, %)`, | ||||
|     angle = -176, | ||||
|     offset = 9, | ||||
|     intersectTag = a | ||||
|         }, %)`, | ||||
|         ang: ang + 180, | ||||
|         locator: '[data-overlay-toolbar-index="11"]', | ||||
|       }) | ||||
| @ -685,55 +696,59 @@ part001 = startSketchOn('XZ') | ||||
|         }, | ||||
|         constraintType: 'intersectionOffset', | ||||
|         expectBeforeUnconstrained: `angledLineThatIntersects({ | ||||
|       angle = -176, | ||||
|       offset = 9, | ||||
|       intersectTag = a | ||||
|     }, %)`, | ||||
|     angle = -176, | ||||
|     offset = 9, | ||||
|     intersectTag = a | ||||
|         }, %)`, | ||||
|         expectAfterUnconstrained: `angledLineThatIntersects({ | ||||
|       angle = -176, | ||||
|       offset = perpDist001, | ||||
|       intersectTag = a | ||||
|     }, %)`, | ||||
|     angle = -176, | ||||
|     offset = perpDist001, | ||||
|     intersectTag = a | ||||
|         }, %)`, | ||||
|         expectFinal: `angledLineThatIntersects({ | ||||
|       angle = -176, | ||||
|       offset = 9, | ||||
|       intersectTag = a | ||||
|     }, %)`, | ||||
|     angle = -176, | ||||
|     offset = 9, | ||||
|     intersectTag = a | ||||
|         }, %)`, | ||||
|         ang: ang + 180, | ||||
|         locator: '[data-overlay-toolbar-index="11"]', | ||||
|       }) | ||||
|     }) | ||||
|     test('for segment [tangentialArcTo]', async ({ page }) => { | ||||
|     test('for segment [tangentialArcTo]', async ({ | ||||
|       page, | ||||
|       editor, | ||||
|       homePage, | ||||
|     }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([0.5, -14 + 0], %) | ||||
|     |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|     |> lineTo([33, 11.5 + 0], %) | ||||
|     |> xLineTo(9 - 5, %) | ||||
|     |> yLineTo(-10.77, %, $a) | ||||
|     |> xLine(26.04, %) | ||||
|     |> yLine(21.14 + 0, %) | ||||
|     |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|     |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|     |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|     |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|     |> angledLineThatIntersects({ | ||||
|           angle = 4.14, | ||||
|           intersectTag = a, | ||||
|           offset = 9 | ||||
|         }, %) | ||||
|     |> tangentialArcTo([3.14 + 13, -3.14], %) | ||||
|         ` | ||||
|         |> startProfileAt([0, 0], %) | ||||
|         |> line([0.5, -14 + 0], %) | ||||
|         |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|         |> lineTo([33, 11.5 + 0], %) | ||||
|         |> xLineTo(9 - 5, %) | ||||
|         |> yLineTo(-10.77, %, $a) | ||||
|         |> xLine(26.04, %) | ||||
|         |> yLine(21.14 + 0, %) | ||||
|         |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|         |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|         |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|         |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|         |> angledLineThatIntersects({ | ||||
|         angle = 4.14, | ||||
|         intersectTag = a, | ||||
|         offset = 9 | ||||
|       }, %) | ||||
|         |> tangentialArcTo([3.14 + 13, -3.14], %) | ||||
|       ` | ||||
|         ) | ||||
|         localStorage.setItem('disableAxis', 'true') | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -747,8 +762,8 @@ part001 = startSketchOn('XZ') | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(13) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page) | ||||
|       const clickConstrained = _clickConstrained(page) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|       const clickConstrained = _clickConstrained(page, editor) | ||||
|  | ||||
|       const tangentialArcTo = await u.getBoundingBox( | ||||
|         '[data-overlay-index="12"]' | ||||
| @ -777,20 +792,20 @@ part001 = startSketchOn('XZ') | ||||
|         locator: '[data-overlay-toolbar-index="12"]', | ||||
|       }) | ||||
|     }) | ||||
|     test('for segment [circle]', async ({ page }) => { | ||||
|     test('for segment [circle]', async ({ page, editor, homePage }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|   |> circle({ center = [1 + 0, 0], radius = 8 }, %) | ||||
| ` | ||||
|       |> circle({ center = [1 + 0, 0], radius = 8 }, %) | ||||
|     ` | ||||
|         ) | ||||
|         localStorage.setItem('disableAxis', 'true') | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -806,8 +821,8 @@ part001 = startSketchOn('XZ') | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(1) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page) | ||||
|       const clickConstrained = _clickConstrained(page) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|       const clickConstrained = _clickConstrained(page, editor) | ||||
|  | ||||
|       const hoverPos = { x: 789, y: 114 } as const | ||||
|       let ang = await u.getAngle('[data-overlay-index="0"]') | ||||
| @ -833,8 +848,8 @@ part001 = startSketchOn('XZ') | ||||
|         expectAfterUnconstrained: | ||||
|           'circle({ center = [xAbs001, yAbs001], radius =  8 }, %)', | ||||
|         expectFinal: 'circle({ center = [xAbs001, 0], radius =  8 }, %)', | ||||
|         ang: ang + 105, | ||||
|         steps: 10, | ||||
|         ang: ang + 180, | ||||
|         steps: 30, | ||||
|         locator: '[data-overlay-toolbar-index="0"]', | ||||
|       }) | ||||
|       console.log('circle radius') | ||||
| @ -854,7 +869,7 @@ part001 = startSketchOn('XZ') | ||||
|   }) | ||||
|   test.describe('Testing deleting a segment', () => { | ||||
|     const _deleteSegmentSequence = | ||||
|       (page: Page) => | ||||
|       (page: Page, editor: EditorFixture) => | ||||
|       async ({ | ||||
|         hoverPos, | ||||
|         codeToBeDeleted, | ||||
| @ -880,47 +895,51 @@ part001 = startSketchOn('XZ') | ||||
|         y = hoverPos.y - Math.sin(ang * deg) * 32 | ||||
|         await page.mouse.move(x, y) | ||||
|         await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator) | ||||
|         await page.mouse.move(x, y) | ||||
|  | ||||
|         await expect(page.locator('.cm-content')).toContainText(codeToBeDeleted) | ||||
|         await editor.expectEditor.toContain(codeToBeDeleted, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|  | ||||
|         await page.locator(`[data-stdlib-fn-name="${stdLibFnName}"]`).click() | ||||
|         await page.getByText('Delete Segment').click() | ||||
|  | ||||
|         await expect(page.locator('.cm-content')).not.toContainText( | ||||
|           codeToBeDeleted | ||||
|         ) | ||||
|         await editor.expectEditor.not.toContain(codeToBeDeleted, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
|       } | ||||
|     test('all segment types', async ({ page }) => { | ||||
|     test('all segment types', async ({ page, editor, homePage }) => { | ||||
|       await page.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([0.5, -14 + 0], %) | ||||
|   |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|   |> lineTo([33, 11.5 + 0], %) | ||||
|   |> xLineTo(9 - 5, %) | ||||
|   |> yLineTo(-10.77, %, $a) | ||||
|   |> xLine(26.04, %) | ||||
|   |> yLine(21.14 + 0, %) | ||||
|   |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|   |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|   |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|   |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|   |> angledLineThatIntersects({ | ||||
|        angle = 4.14, | ||||
|        intersectTag = a, | ||||
|        offset = 9 | ||||
|      }, %) | ||||
|   |> tangentialArcTo([3.14 + 13, 1.14], %) | ||||
|         ` | ||||
|       |> startProfileAt([0, 0], %) | ||||
|       |> line([0.5, -14 + 0], %) | ||||
|       |> angledLine({ angle = 3 + 0, length = 32 + 0 }, %) | ||||
|       |> lineTo([33, 11.5 + 0], %) | ||||
|       |> xLineTo(9 - 5, %) | ||||
|       |> yLineTo(-10.77, %, $a) | ||||
|       |> xLine(26.04, %) | ||||
|       |> yLine(21.14 + 0, %) | ||||
|       |> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %) | ||||
|       |> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %) | ||||
|       |> angledLineToX({ angle = 3 + 0, to = 26 }, %) | ||||
|       |> angledLineToY({ angle = 89, to = 9.14 + 0 }, %) | ||||
|       |> angledLineThatIntersects({ | ||||
|      angle = 4.14, | ||||
|      intersectTag = a, | ||||
|      offset = 9 | ||||
|          }, %) | ||||
|       |> tangentialArcTo([3.14 + 13, 1.14], %) | ||||
|       ` | ||||
|         ) | ||||
|         localStorage.setItem('disableAxis', 'true') | ||||
|       }) | ||||
|       const u = await getUtils(page) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.waitForPageLoad() | ||||
|  | ||||
|       // wait for execution done | ||||
|       await u.openDebugPanel() | ||||
| @ -933,7 +952,7 @@ part001 = startSketchOn('XZ') | ||||
|       await page.waitForTimeout(500) | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(13) | ||||
|       const deleteSegmentSequence = _deleteSegmentSequence(page) | ||||
|       const deleteSegmentSequence = _deleteSegmentSequence(page, editor) | ||||
|  | ||||
|       let segmentToDelete | ||||
|  | ||||
| @ -958,7 +977,7 @@ part001 = startSketchOn('XZ') | ||||
|       angle = 4.14, | ||||
|       intersectTag = a, | ||||
|       offset = 9 | ||||
|     }, %)`, | ||||
|         }, %)`, | ||||
|         stdLibFnName: 'angledLineThatIntersects', | ||||
|         ang: ang + 180, | ||||
|         steps: 7, | ||||
| @ -1066,16 +1085,19 @@ part001 = startSketchOn('XZ') | ||||
|         5, | ||||
|         '[data-overlay-toolbar-index="2"]' | ||||
|       ) | ||||
|       await page.mouse.move(hoverPos.x, hoverPos.y) | ||||
|  | ||||
|       const codeToBeDeleted = 'lineTo([33, 11.5 + 0], %)' | ||||
|       await expect(page.locator('.cm-content')).toContainText(codeToBeDeleted) | ||||
|       await editor.expectEditor.toContain(codeToBeDeleted, { | ||||
|         shouldNormalise: true, | ||||
|       }) | ||||
|  | ||||
|       await page.getByTestId('overlay-menu').click() | ||||
|       await page.getByText('Delete Segment').click() | ||||
|  | ||||
|       await expect(page.locator('.cm-content')).not.toContainText( | ||||
|         codeToBeDeleted | ||||
|       ) | ||||
|       await editor.expectEditor.not.toContain(codeToBeDeleted, { | ||||
|         shouldNormalise: true, | ||||
|       }) | ||||
|  | ||||
|       segmentToDelete = await getOverlayByIndex(1) | ||||
|       ang = await u.getAngle(`[data-overlay-index="${1}"]`) | ||||
| @ -1121,17 +1143,17 @@ part001 = startSketchOn('XZ') | ||||
|         const isObj = lineOfInterest.includes('{ angle = 3,') | ||||
|         test(`${lineOfInterest.split('(')[0]}${isObj ? '-[obj-input]' : ''}${ | ||||
|           doesHaveTagOutsideSketch ? '-[tagOutsideSketch]' : '' | ||||
|         }`, async ({ page }) => { | ||||
|         }`, async ({ page, editor, homePage }) => { | ||||
|           await page.addInitScript( | ||||
|             async ({ lineToBeDeleted, extraLine }) => { | ||||
|               localStorage.setItem( | ||||
|                 'persistCode', | ||||
|                 `part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([5, 6], %) | ||||
|   |> ${lineToBeDeleted} | ||||
|   |> line([-10, -15], %) | ||||
|   |> angledLine([-176, segLen(seg01)], %)         | ||||
| ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|           |> startProfileAt([5, 6], %) | ||||
|           |> ${lineToBeDeleted} | ||||
|           |> line([-10, -15], %) | ||||
|           |> angledLine([-176, segLen(seg01)], %)         | ||||
|         ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|               ) | ||||
|             }, | ||||
|             { | ||||
| @ -1142,7 +1164,7 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|           const u = await getUtils(page) | ||||
|           await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|           await u.waitForAuthSkipAppStart() | ||||
|           await homePage.goToModelingScene() | ||||
|           await page.waitForTimeout(300) | ||||
|  | ||||
|           await page.getByText(lineOfInterest).click() | ||||
| @ -1170,9 +1192,9 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|           await page.mouse.move(hoverPos.x + x, hoverPos.y + y) | ||||
|           await page.mouse.move(hoverPos.x, hoverPos.y, { steps: 5 }) | ||||
|  | ||||
|           await expect(page.locator('.cm-content')).toContainText( | ||||
|             lineOfInterest | ||||
|           ) | ||||
|           await editor.expectEditor.toContain(lineOfInterest, { | ||||
|             shouldNormalise: true, | ||||
|           }) | ||||
|  | ||||
|           await page.getByTestId('overlay-menu').click() | ||||
|           await page.waitForTimeout(100) | ||||
| @ -1183,9 +1205,9 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|           await page.mouse.move(hoverPos.x + x, hoverPos.y + y) | ||||
|           await page.mouse.move(hoverPos.x, hoverPos.y, { steps: 5 }) | ||||
|  | ||||
|           await expect(page.locator('.cm-content')).toContainText( | ||||
|             lineOfInterest | ||||
|           ) | ||||
|           await editor.expectEditor.toContain(lineOfInterest, { | ||||
|             shouldNormalise: true, | ||||
|           }) | ||||
|  | ||||
|           await page.getByTestId('overlay-menu').click() | ||||
|           await page.waitForTimeout(100) | ||||
| @ -1201,16 +1223,18 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|               ) | ||||
|             ).toBeTruthy() | ||||
|             // eslint-disable-next-line jest/no-conditional-expect | ||||
|             await expect(page.locator('.cm-content')).toContainText( | ||||
|               lineOfInterest | ||||
|             ) | ||||
|             await editor.expectEditor.toContain(lineOfInterest, { | ||||
|               shouldNormalise: true, | ||||
|             }) | ||||
|           } else { | ||||
|             // eslint-disable-next-line jest/no-conditional-expect | ||||
|             await expect(page.locator('.cm-content')).not.toContainText( | ||||
|               lineOfInterest | ||||
|             ) | ||||
|             await editor.expectEditor.not.toContain(lineOfInterest, { | ||||
|               shouldNormalise: true, | ||||
|             }) | ||||
|             // eslint-disable-next-line jest/no-conditional-expect | ||||
|             await expect(page.locator('.cm-content')).not.toContainText('seg01') | ||||
|             await editor.expectEditor.not.toContain('seg01', { | ||||
|               shouldNormalise: true, | ||||
|             }) | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
| @ -1281,16 +1305,18 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|       const isObj = before.includes('{ angle = 3') | ||||
|       test(`${before.split('(')[0]}${isObj ? '-[obj-input]' : ''}`, async ({ | ||||
|         page, | ||||
|         editor, | ||||
|         homePage, | ||||
|       }) => { | ||||
|         await page.addInitScript( | ||||
|           async ({ lineToBeDeleted }) => { | ||||
|             localStorage.setItem( | ||||
|               'persistCode', | ||||
|               `part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([5, 6], %) | ||||
|   |> ${lineToBeDeleted} | ||||
|   |> line([-10, -15], %) | ||||
|   |> angledLine([-176, segLen(seg01)], %)` | ||||
|         |> startProfileAt([5, 6], %) | ||||
|         |> ${lineToBeDeleted} | ||||
|         |> line([-10, -15], %) | ||||
|         |> angledLine([-176, segLen(seg01)], %)` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
| @ -1300,7 +1326,8 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|         const u = await getUtils(page) | ||||
|         await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|         await u.waitForAuthSkipAppStart() | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|         await page.waitForTimeout(300) | ||||
|  | ||||
|         await page.getByText(before).click() | ||||
| @ -1333,14 +1360,16 @@ ${extraLine ? 'myVar = segLen(seg01)' : ''}` | ||||
|           5, | ||||
|           '[data-overlay-toolbar-index="0"]' | ||||
|         ) | ||||
|         await page.mouse.move(x, y) | ||||
|  | ||||
|         await expect(page.locator('.cm-content')).toContainText(before) | ||||
|         await editor.expectEditor.toContain(before, { shouldNormalise: true }) | ||||
|  | ||||
|         await page.getByTestId('overlay-menu').click() | ||||
|         await page.waitForTimeout(100) | ||||
|         await page.getByText('Remove constraints').click() | ||||
|  | ||||
|         await expect(page.locator('.cm-content')).toContainText(after) | ||||
|         await editor.expectEditor.toContain(after, { shouldNormalise: true }) | ||||
|  | ||||
|         // check the cursor was left in the correct place after transform | ||||
|         await expect(page.locator('.cm-activeLine')).toHaveText('|> ' + after) | ||||
|         await expect(page.getByTestId('segment-overlay')).toHaveCount(3) | ||||
|  | ||||
| @ -1,24 +1,16 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| import { commonPoints, getUtils, setup, tearDown } from './test-utils' | ||||
| import { commonPoints, getUtils } from './test-utils' | ||||
| import { Coords2d } from 'lang/std/sketch' | ||||
| import { KCL_DEFAULT_LENGTH } from 'lib/constants' | ||||
| 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('Testing selections', () => { | ||||
|   test.setTimeout(90_000) | ||||
|   test( | ||||
|     'Selections work on fresh and edited sketch', | ||||
|     { tag: ['@skipWin'] }, | ||||
|     async ({ page }) => { | ||||
|     async ({ page, homePage }) => { | ||||
|       // Skip on windows its being weird. | ||||
|       test.skip(process.platform === 'win32', 'Skip on windows') | ||||
|  | ||||
| @ -27,9 +19,9 @@ test.describe('Testing selections', () => { | ||||
|       // source ranges are wrong, hovers won't work | ||||
|       const u = await getUtils(page) | ||||
|       const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.openDebugPanel() | ||||
|  | ||||
|       const yAxisClick = () => | ||||
| @ -79,31 +71,31 @@ test.describe('Testing selections', () => { | ||||
|       await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt(${commonPoints.startAt}, %)`) | ||||
|       |> startProfileAt(${commonPoints.startAt}, %)`) | ||||
|  | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|  | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt(${commonPoints.startAt}, %) | ||||
|     |> xLine(${commonPoints.num1}, %)`) | ||||
|       |> startProfileAt(${commonPoints.startAt}, %) | ||||
|       |> xLine(${commonPoints.num1}, %)`) | ||||
|  | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt(${commonPoints.startAt}, %) | ||||
|     |> xLine(${commonPoints.num1}, %) | ||||
|     |> yLine(${commonPoints.num1 + 0.01}, %)`) | ||||
|       |> startProfileAt(${commonPoints.startAt}, %) | ||||
|       |> xLine(${commonPoints.num1}, %) | ||||
|       |> yLine(${commonPoints.num1 + 0.01}, %)`) | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.mouse.click(startXPx, 500 - PUR * 20) | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt(${commonPoints.startAt}, %) | ||||
|     |> xLine(${commonPoints.num1}, %) | ||||
|     |> yLine(${commonPoints.num1 + 0.01}, %) | ||||
|     |> xLine(${commonPoints.num2 * -1}, %)`) | ||||
|       |> startProfileAt(${commonPoints.startAt}, %) | ||||
|       |> xLine(${commonPoints.num1}, %) | ||||
|       |> yLine(${commonPoints.num1 + 0.01}, %) | ||||
|       |> xLine(${commonPoints.num2 * -1}, %)`) | ||||
|  | ||||
|       // deselect line tool | ||||
|       await page.getByRole('button', { name: 'line Line', exact: true }).click() | ||||
| @ -264,78 +256,78 @@ test.describe('Testing selections', () => { | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test('Solids should be select and deletable', async ({ page }) => { | ||||
|   test('Solids should be select and deletable', async ({ page, homePage }) => { | ||||
|     test.setTimeout(90_000) | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|         |> startProfileAt([-79.26, 95.04], %) | ||||
|         |> line([112.54, 127.64], %, $seg02) | ||||
|         |> line([170.36, -121.61], %, $seg01) | ||||
|         |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|         |> close(%) | ||||
| extrude001 = extrude(50, sketch001) | ||||
| sketch005 = startSketchOn(extrude001, 'END') | ||||
|   |> startProfileAt([23.24, 136.52], %) | ||||
|   |> line([-8.44, 36.61], %) | ||||
|   |> line([49.4, 2.05], %) | ||||
|   |> line([29.69, -46.95], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| sketch003 = startSketchOn(extrude001, seg01) | ||||
|   |> startProfileAt([21.23, 17.81], %) | ||||
|   |> line([51.97, 21.32], %) | ||||
|   |> line([4.07, -22.75], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| sketch002 = startSketchOn(extrude001, seg02) | ||||
|   |> startProfileAt([-100.54, 16.99], %) | ||||
|   |> line([0, 20.03], %) | ||||
|   |> line([62.61, 0], %, $seg03) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude002 = extrude(50, sketch002) | ||||
| sketch004 = startSketchOn(extrude002, seg03) | ||||
|   |> startProfileAt([57.07, 134.77], %) | ||||
|   |> line([-4.72, 22.84], %) | ||||
|   |> line([28.8, 6.71], %) | ||||
|   |> line([9.19, -25.33], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude003 = extrude(20, sketch004) | ||||
| pipeLength = 40 | ||||
| pipeSmallDia = 10 | ||||
| pipeLargeDia = 20 | ||||
| thickness = 0.5 | ||||
| part009 = startSketchOn('XY') | ||||
|   |> startProfileAt([pipeLargeDia - (thickness / 2), 38], %) | ||||
|   |> line([thickness, 0], %) | ||||
|   |> line([0, -1], %) | ||||
|   |> angledLineToX({ | ||||
|        angle = 60, | ||||
|        to = pipeSmallDia + thickness | ||||
|      }, %) | ||||
|   |> line([0, -pipeLength], %) | ||||
|   |> angledLineToX({ | ||||
|        angle = -60, | ||||
|        to = pipeLargeDia + thickness | ||||
|      }, %) | ||||
|   |> line([0, -1], %) | ||||
|   |> line([-thickness, 0], %) | ||||
|   |> line([0, 1], %) | ||||
|   |> angledLineToX({ angle = 120, to = pipeSmallDia }, %) | ||||
|   |> line([0, pipeLength], %) | ||||
|   |> angledLineToX({ angle = 60, to = pipeLargeDia }, %) | ||||
|   |> close(%) | ||||
| rev = revolve({ axis = 'y' }, part009) | ||||
| ` | ||||
|       |> startProfileAt([-79.26, 95.04], %) | ||||
|       |> line([112.54, 127.64], %, $seg02) | ||||
|       |> line([170.36, -121.61], %, $seg01) | ||||
|       |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|       |> close(%) | ||||
|   extrude001 = extrude(50, sketch001) | ||||
|   sketch005 = startSketchOn(extrude001, 'END') | ||||
|     |> startProfileAt([23.24, 136.52], %) | ||||
|     |> line([-8.44, 36.61], %) | ||||
|     |> line([49.4, 2.05], %) | ||||
|     |> line([29.69, -46.95], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   sketch003 = startSketchOn(extrude001, seg01) | ||||
|     |> startProfileAt([21.23, 17.81], %) | ||||
|     |> line([51.97, 21.32], %) | ||||
|     |> line([4.07, -22.75], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   sketch002 = startSketchOn(extrude001, seg02) | ||||
|     |> startProfileAt([-100.54, 16.99], %) | ||||
|     |> line([0, 20.03], %) | ||||
|     |> line([62.61, 0], %, $seg03) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude002 = extrude(50, sketch002) | ||||
|   sketch004 = startSketchOn(extrude002, seg03) | ||||
|     |> startProfileAt([57.07, 134.77], %) | ||||
|     |> line([-4.72, 22.84], %) | ||||
|     |> line([28.8, 6.71], %) | ||||
|     |> line([9.19, -25.33], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude003 = extrude(20, sketch004) | ||||
|   pipeLength = 40 | ||||
|   pipeSmallDia = 10 | ||||
|   pipeLargeDia = 20 | ||||
|   thickness = 0.5 | ||||
|   part009 = startSketchOn('XY') | ||||
|     |> startProfileAt([pipeLargeDia - (thickness / 2), 38], %) | ||||
|     |> line([thickness, 0], %) | ||||
|     |> line([0, -1], %) | ||||
|     |> angledLineToX({ | ||||
|      angle = 60, | ||||
|      to = pipeSmallDia + thickness | ||||
|    }, %) | ||||
|     |> line([0, -pipeLength], %) | ||||
|     |> angledLineToX({ | ||||
|      angle = -60, | ||||
|      to = pipeLargeDia + thickness | ||||
|    }, %) | ||||
|     |> line([0, -1], %) | ||||
|     |> line([-thickness, 0], %) | ||||
|     |> line([0, 1], %) | ||||
|     |> angledLineToX({ angle = 120, to = pipeSmallDia }, %) | ||||
|     |> line([0, pipeLength], %) | ||||
|     |> angledLineToX({ angle = 60, to = pipeLargeDia }, %) | ||||
|     |> close(%) | ||||
|   rev = revolve({ axis: 'y' }, part009) | ||||
|   ` | ||||
|       ) | ||||
|     }, KCL_DEFAULT_LENGTH) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.goto('/') | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
| @ -395,29 +387,29 @@ rev = revolve({ axis = 'y' }, part009) | ||||
|       `extrude001 = extrude(50, sketch001)` | ||||
|     ) | ||||
|     await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({ | ||||
|        plane = { | ||||
|          origin = { x = 0, y = -50, z = 0 }, | ||||
|          x_axis = { x = 1, y = 0, z = 0 }, | ||||
|          y_axis = { x = 0, y = 0, z = 1 }, | ||||
|          z_axis = { x = 0, y = -1, z = 0 } | ||||
|        } | ||||
|      })`) | ||||
|      plane = { | ||||
|        origin = { x = 0, y = -50, z = 0 }, | ||||
|        x_axis = { x = 1, y = 0, z = 0 }, | ||||
|        y_axis = { x = 0, y = 0, z = 1 }, | ||||
|        z_axis = { x = 0, y = -1, z = 0 } | ||||
|      } | ||||
|    })`) | ||||
|     await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({ | ||||
|        plane = { | ||||
|          origin = { x = 116.53, y = 0, z = 163.25 }, | ||||
|          x_axis = { x = -0.81, y = 0, z = 0.58 }, | ||||
|          y_axis = { x = 0, y = -1, z = 0 }, | ||||
|          z_axis = { x = 0.58, y = 0, z = 0.81 } | ||||
|        } | ||||
|      })`) | ||||
|      plane = { | ||||
|        origin = { x = 116.53, y = 0, z = 163.25 }, | ||||
|        x_axis = { x = -0.81, y = 0, z = 0.58 }, | ||||
|        y_axis = { x = 0, y = -1, z = 0 }, | ||||
|        z_axis = { x = 0.58, y = 0, z = 0.81 } | ||||
|      } | ||||
|    })`) | ||||
|     await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({ | ||||
|        plane = { | ||||
|          origin = { x = -91.74, y = 0, z = 80.89 }, | ||||
|          x_axis = { x = -0.66, y = 0, z = -0.75 }, | ||||
|          y_axis = { x = 0, y = -1, z = 0 }, | ||||
|          z_axis = { x = -0.75, y = 0, z = 0.66 } | ||||
|        } | ||||
|      })`) | ||||
|      plane = { | ||||
|        origin = { x = -91.74, y = 0, z = 80.89 }, | ||||
|        x_axis = { x = -0.66, y = 0, z = -0.75 }, | ||||
|        y_axis = { x = 0, y = -1, z = 0 }, | ||||
|        z_axis = { x = -0.75, y = 0, z = 0.66 } | ||||
|      } | ||||
|    })`) | ||||
|  | ||||
|     // DELETE SOLID 2D | ||||
|     await page.mouse.click(solid2d.x, solid2d.y) | ||||
| @ -433,31 +425,32 @@ rev = revolve({ axis = 'y' }, part009) | ||||
|   }) | ||||
|   test("Deleting solid that the AST mod can't handle results in a toast message", async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-79.26, 95.04], %) | ||||
|   |> line([112.54, 127.64], %, $seg02) | ||||
|   |> line([170.36, -121.61], %, $seg01) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(50, sketch001) | ||||
| launderExtrudeThroughVar = extrude001 | ||||
| sketch002 = startSketchOn(launderExtrudeThroughVar, seg02) | ||||
|   |> startProfileAt([-100.54, 16.99], %) | ||||
|   |> line([0, 20.03], %) | ||||
|   |> line([62.61, 0], %, $seg03) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| ` | ||||
|     |> startProfileAt([-79.26, 95.04], %) | ||||
|     |> line([112.54, 127.64], %, $seg02) | ||||
|     |> line([170.36, -121.61], %, $seg01) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(50, sketch001) | ||||
|   launderExtrudeThroughVar = extrude001 | ||||
|   sketch002 = startSketchOn(launderExtrudeThroughVar, seg02) | ||||
|     |> startProfileAt([-100.54, 16.99], %) | ||||
|     |> line([0, 20.03], %) | ||||
|     |> line([62.61, 0], %, $seg03) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   ` | ||||
|       ) | ||||
|     }, KCL_DEFAULT_LENGTH) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.goto('/') | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]', 10_000) | ||||
| @ -497,37 +490,38 @@ sketch002 = startSketchOn(launderExtrudeThroughVar, seg02) | ||||
|   }) | ||||
|   test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async (KCL_DEFAULT_LENGTH) => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([20, 0], %) | ||||
|     |> line([7.13, 4 + 0], %) | ||||
|     |> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %) | ||||
|     |> lineTo([20.14 + 0, -0.14 + 0], %) | ||||
|     |> xLineTo(29 + 0, %) | ||||
|     |> yLine(-3.14 + 0, %, $a) | ||||
|     |> xLine(1.63, %) | ||||
|     |> angledLineOfXLength({ angle = 3 + 0, length = 3.14 }, %) | ||||
|     |> angledLineOfYLength({ angle = 30, length = 3 + 0 }, %) | ||||
|     |> angledLineToX({ angle = 22.14 + 0, to = 12 }, %) | ||||
|     |> angledLineToY({ angle = 30, to = 11.14 }, %) | ||||
|     |> angledLineThatIntersects({ | ||||
|           angle = 3.14, | ||||
|           intersectTag = a, | ||||
|           offset = 0 | ||||
|         }, %) | ||||
|     |> tangentialArcTo([13.14 + 0, 13.14], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5 + 7, %) | ||||
|   ` | ||||
|   |> startProfileAt([20, 0], %) | ||||
|   |> line([7.13, 4 + 0], %) | ||||
|   |> angledLine({ angle = 3 + 0, length = 3.14 + 0 }, %) | ||||
|   |> lineTo([20.14 + 0, -0.14 + 0], %) | ||||
|   |> xLineTo(29 + 0, %) | ||||
|   |> yLine(-3.14 + 0, %, $a) | ||||
|   |> xLine(1.63, %) | ||||
|   |> angledLineOfXLength({ angle = 3 + 0, length = 3.14 }, %) | ||||
|   |> angledLineOfYLength({ angle = 30, length = 3 + 0 }, %) | ||||
|   |> angledLineToX({ angle = 22.14 + 0, to = 12 }, %) | ||||
|   |> angledLineToY({ angle = 30, to = 11.14 }, %) | ||||
|   |> angledLineThatIntersects({ | ||||
|         angle = 3.14, | ||||
|         intersectTag = a, | ||||
|         offset = 0 | ||||
|       }, %) | ||||
|   |> tangentialArcTo([13.14 + 0, 13.14], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5 + 7, %) | ||||
|     ` | ||||
|       ) | ||||
|     }, KCL_DEFAULT_LENGTH) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -727,29 +721,29 @@ sketch002 = startSketchOn(launderExtrudeThroughVar, seg02) | ||||
|  | ||||
|     await u.removeCurrentCode() | ||||
|     await u.codeLocator.fill(`sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag] | ||||
|   |> angledLine([0, 268.43], %, $rectangleSegmentA001) | ||||
|   |> angledLine([ | ||||
|        segAng(rectangleSegmentA001) - 90, | ||||
|        217.26 | ||||
|      ], %, $seg01) | ||||
|   |> angledLine([ | ||||
|        segAng(rectangleSegmentA001), | ||||
|        -segLen(rectangleSegmentA001) | ||||
|      ], %, $yo) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %, $seg02) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(100, sketch001) | ||||
|   |> chamfer({ | ||||
|        length = 30, | ||||
|        tags = [ | ||||
|          seg01, | ||||
|          getNextAdjacentEdge(yo), | ||||
|          getNextAdjacentEdge(seg02), | ||||
|          getOppositeEdge(seg01) | ||||
|        ] | ||||
|      }, %) | ||||
| `) | ||||
|     |> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag] | ||||
|     |> angledLine([0, 268.43], %, $rectangleSegmentA001) | ||||
|     |> angledLine([ | ||||
|      segAng(rectangleSegmentA001) - 90, | ||||
|      217.26 | ||||
|    ], %, $seg01) | ||||
|     |> angledLine([ | ||||
|      segAng(rectangleSegmentA001), | ||||
|      -segLen(rectangleSegmentA001) | ||||
|    ], %, $yo) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %, $seg02) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(100, sketch001) | ||||
|     |> chamfer({ | ||||
|      length = 30, | ||||
|      tags = [ | ||||
|        seg01, | ||||
|        getNextAdjacentEdge(yo), | ||||
|        getNextAdjacentEdge(seg02), | ||||
|        getOppositeEdge(seg01) | ||||
|      ] | ||||
|    }, %) | ||||
|   `) | ||||
|     await expect( | ||||
|       page.getByTestId('model-state-indicator-execution-done') | ||||
|     ).toBeVisible() | ||||
| @ -837,39 +831,45 @@ extrude001 = extrude(100, sketch001) | ||||
|   }) | ||||
|   test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({ | ||||
|     page, | ||||
|     editor, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([3.29, 7.86], %) | ||||
|   |> line([2.48, 2.44], %) | ||||
|   |> line([2.66, 1.17], %) | ||||
|   |> line([3.75, 0.46], %) | ||||
|   |> line([4.99, -0.46], %, $seg01) | ||||
|   |> line([3.3, -2.12], %) | ||||
|   |> line([2.16, -3.33], %) | ||||
|   |> line([0.85, -3.08], %) | ||||
|   |> line([-0.18, -3.36], %) | ||||
|   |> line([-3.86, -2.73], %) | ||||
|   |> line([-17.67, 0.85], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(10, sketch001) | ||||
|   ` | ||||
|     |> startProfileAt([3.29, 7.86], %) | ||||
|     |> line([2.48, 2.44], %) | ||||
|     |> line([2.66, 1.17], %) | ||||
|     |> line([3.75, 0.46], %) | ||||
|     |> line([4.99, -0.46], %, $seg01) | ||||
|     |> line([3.3, -2.12], %) | ||||
|     |> line([2.16, -3.33], %) | ||||
|     |> line([0.85, -3.08], %) | ||||
|     |> line([-0.18, -3.36], %) | ||||
|     |> line([-3.86, -2.73], %) | ||||
|     |> line([-17.67, 0.85], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(10, sketch001) | ||||
|     ` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     const selectUnExtrudable = () => | ||||
|       page.getByText(`line([4.99, -0.46], %, $seg01)`).click() | ||||
|     const selectUnExtrudable = async () => { | ||||
|       await editor.scrollToText(`line([4.99, -0.46], %, $seg01)`) | ||||
|       await page.getByText(`line([4.99, -0.46], %, $seg01)`).click() | ||||
|     } | ||||
|     const clickEmpty = () => page.mouse.click(700, 460) | ||||
|     await selectUnExtrudable() | ||||
|     // expect extrude button to be disabled | ||||
| @ -879,17 +879,18 @@ extrude001 = extrude(10, sketch001) | ||||
|  | ||||
|     // expect active line to contain nothing | ||||
|     await expect(page.locator('.cm-activeLine')).toHaveText('') | ||||
|  | ||||
|     // and extrude to still be disabled | ||||
|     await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||
|  | ||||
|     const codeToAdd = `${await u.codeLocator.allInnerTexts()} | ||||
| sketch002 = startSketchOn(extrude001, $seg01) | ||||
|   |> startProfileAt([-12.94, 6.6], %) | ||||
|   |> line([2.45, -0.2], %) | ||||
|   |> line([-2, -1.25], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| ` | ||||
|   sketch002 = startSketchOn(extrude001, $seg01) | ||||
|     |> startProfileAt([-12.94, 6.6], %) | ||||
|     |> line([2.45, -0.2], %) | ||||
|     |> line([-2, -1.25], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   ` | ||||
|     await u.codeLocator.fill(codeToAdd) | ||||
|  | ||||
|     await selectUnExtrudable() | ||||
| @ -904,23 +905,23 @@ sketch002 = startSketchOn(extrude001, $seg01) | ||||
|     ).not.toBeDisabled() | ||||
|   }) | ||||
|  | ||||
|   test('Fillet button states test', async ({ page }) => { | ||||
|   test('Fillet button states test', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-5, -5], %) | ||||
|   |> line([0, 10], %) | ||||
|   |> line([10, 0], %) | ||||
|   |> line([0, -10], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%)` | ||||
|     |> startProfileAt([-5, -5], %) | ||||
|     |> line([0, 10], %) | ||||
|     |> line([10, 0], %) | ||||
|     |> line([0, -10], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
| @ -937,7 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01) | ||||
|  | ||||
|     // test fillet button with the body in the scene | ||||
|     const codeToAdd = `${await u.codeLocator.allInnerTexts()} | ||||
| extrude001 = extrude(10, sketch001)` | ||||
|   extrude001 = extrude(10, sketch001)` | ||||
|     await u.codeLocator.clear() | ||||
|     await u.codeLocator.fill(codeToAdd) | ||||
|     await selectSegment() | ||||
| @ -958,6 +959,7 @@ extrude001 = extrude(10, sketch001)` | ||||
|  | ||||
|   test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const cases = [ | ||||
|       { | ||||
| @ -978,21 +980,21 @@ extrude001 = extrude(10, sketch001)` | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `yo = 79 | ||||
| part001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-7.54, -26.74], %) | ||||
|   |> ${cases[0].expectedCode} | ||||
|   |> line([-3.19, -138.43], %) | ||||
|   |> ${cases[1].expectedCode} | ||||
|   |> line([41.19, 28.97 + 5], %) | ||||
|   |> ${cases[2].expectedCode}` | ||||
|   part001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([-7.54, -26.74], %) | ||||
|     |> ${cases[0].expectedCode} | ||||
|     |> line([-3.19, -138.43], %) | ||||
|     |> ${cases[1].expectedCode} | ||||
|     |> line([41.19, 28.97 + 5], %) | ||||
|     |> ${cases[2].expectedCode}` | ||||
|         ) | ||||
|       }, | ||||
|       { cases } | ||||
|     ) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openAndClearDebugPanel() | ||||
|  | ||||
|     await u.sendCustomCmd({ | ||||
| @ -1025,24 +1027,25 @@ part001 = startSketchOn('XZ') | ||||
|   }) | ||||
|   test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([-79.26, 95.04], %) | ||||
|   |> line([112.54, 127.64], %) | ||||
|   |> line([170.36, -121.61], %, $seg01) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(50, sketch001) | ||||
|         ` | ||||
|     |> startProfileAt([-79.26, 95.04], %) | ||||
|     |> line([112.54, 127.64], %) | ||||
|     |> line([170.36, -121.61], %, $seg01) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(50, sketch001) | ||||
|       ` | ||||
|       ) | ||||
|     }) | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openAndClearDebugPanel() | ||||
|  | ||||
|     await u.sendCustomCmd({ | ||||
| @ -1125,6 +1128,7 @@ extrude001 = extrude(50, sketch001) | ||||
|   }) | ||||
|   test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     const selectionsSnippets = { | ||||
| @ -1143,46 +1147,46 @@ extrude001 = extrude(50, sketch001) | ||||
|         localStorage.setItem( | ||||
|           'persistCode', | ||||
|           `part001 = startSketchOn('XZ') | ||||
|     ${extrudeAndEditBlocked} | ||||
|     |> line([25.96, 2.93], %) | ||||
|     |> line([5.25, -5.72], %) | ||||
|     |> line([-2.01, -10.35], %) | ||||
|     |> line([-27.65, -2.78], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %) | ||||
|   sketch002 = startSketchOn('XZ') | ||||
|     ${extrudeAndEditAllowed} | ||||
|     |> line([10.32, 6.47], %) | ||||
|     |> line([9.71, -6.16], %) | ||||
|     |> line([-3.08, -9.86], %) | ||||
|     |> line([-12.02, -1.54], %) | ||||
|     |> close(%) | ||||
|   sketch003 = startSketchOn('XZ') | ||||
|     ${editOnly} | ||||
|     |> line([27.55, -1.65], %) | ||||
|     |> line([4.95, -8], %) | ||||
|     |> line([-20.38, -10.12], %) | ||||
|     |> line([-15.79, 17.08], %) | ||||
|  | ||||
|   fn yohey = (pos) => { | ||||
|     sketch004 = startSketchOn('XZ') | ||||
|     ${extrudeAndEditBlockedInFunction} | ||||
|     |> line([27.55, -1.65], %) | ||||
|     |> line([4.95, -10.53], %) | ||||
|     |> line([-20.38, -8], %) | ||||
|     |> line([-15.79, 17.08], %) | ||||
|     return '' | ||||
|   } | ||||
|  | ||||
|       yohey([15.79, -34.6]) | ||||
|   ` | ||||
|   ${extrudeAndEditBlocked} | ||||
|   |> line([25.96, 2.93], %) | ||||
|   |> line([5.25, -5.72], %) | ||||
|   |> line([-2.01, -10.35], %) | ||||
|   |> line([-27.65, -2.78], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %) | ||||
|     sketch002 = startSketchOn('XZ') | ||||
|   ${extrudeAndEditAllowed} | ||||
|   |> line([10.32, 6.47], %) | ||||
|   |> line([9.71, -6.16], %) | ||||
|   |> line([-3.08, -9.86], %) | ||||
|   |> line([-12.02, -1.54], %) | ||||
|   |> close(%) | ||||
|     sketch003 = startSketchOn('XZ') | ||||
|   ${editOnly} | ||||
|   |> line([27.55, -1.65], %) | ||||
|   |> line([4.95, -8], %) | ||||
|   |> line([-20.38, -10.12], %) | ||||
|   |> line([-15.79, 17.08], %) | ||||
|    | ||||
|     fn yohey = (pos) => { | ||||
|   sketch004 = startSketchOn('XZ') | ||||
|   ${extrudeAndEditBlockedInFunction} | ||||
|   |> line([27.55, -1.65], %) | ||||
|   |> line([4.95, -10.53], %) | ||||
|   |> line([-20.38, -8], %) | ||||
|   |> line([-15.79, 17.08], %) | ||||
|   return '' | ||||
|     } | ||||
|    | ||||
|     yohey([15.79, -34.6]) | ||||
|     ` | ||||
|         ) | ||||
|       }, | ||||
|       selectionsSnippets | ||||
|     ) | ||||
|     await page.setViewportSize({ width: 1200, height: 1000 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 1000 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
| @ -1222,6 +1226,7 @@ extrude001 = extrude(50, sketch001) | ||||
|  | ||||
|   test('Deselecting line tool should mean nothing happens on click', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     /** | ||||
|      * If the line tool is clicked when the state is 'No Points' it will exit Sketch mode. | ||||
| @ -1230,9 +1235,9 @@ extrude001 = extrude(50, sketch001) | ||||
|      * To continue to test this workflow, we now enter sketch mode and place a single point before exiting the line tool. | ||||
|      */ | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     await expect( | ||||
|  | ||||
| @ -1,14 +1,7 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
| import * as fsp from 'fs/promises' | ||||
| import { join } from 'path' | ||||
| import { | ||||
|   getUtils, | ||||
|   setup, | ||||
|   setupElectron, | ||||
|   tearDown, | ||||
|   executorInputPath, | ||||
|   createProject, | ||||
| } from './test-utils' | ||||
| import { getUtils, executorInputPath, createProject } from './test-utils' | ||||
| import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes' | ||||
| import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants' | ||||
| import { | ||||
| @ -19,141 +12,136 @@ import { | ||||
| } from './storageStates' | ||||
| import * as TOML from '@iarna/toml' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.describe('Testing settings', () => { | ||||
|   test('Stored settings are validated and fall back to defaults', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|   test( | ||||
|     'Stored settings are validated and fall back to defaults', | ||||
|     // Override beforeEach test setup | ||||
|     // with corrupted settings | ||||
|     await page.addInitScript( | ||||
|       async ({ settingsKey, settings }) => { | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }), | ||||
|       } | ||||
|     ) | ||||
|     { | ||||
|       appSettings: TEST_SETTINGS_CORRUPTED, | ||||
|     }, | ||||
|     async ({ page, homePage }) => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       // Check the settings were reset | ||||
|       const storedSettings = TOML.parse( | ||||
|         await page.evaluate( | ||||
|           ({ settingsKey }) => localStorage.getItem(settingsKey) || '', | ||||
|           { settingsKey: TEST_SETTINGS_KEY } | ||||
|         ) | ||||
|       ) as { settings: SaveSettingsPayload } | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|       expect(storedSettings.settings?.app?.theme).toBe('dark') | ||||
|  | ||||
|     // Check the settings were reset | ||||
|     const storedSettings = TOML.parse( | ||||
|       await page.evaluate( | ||||
|         ({ settingsKey }) => localStorage.getItem(settingsKey) || '', | ||||
|         { settingsKey: TEST_SETTINGS_KEY } | ||||
|       // Check that the invalid settings were changed to good defaults | ||||
|       expect(storedSettings.settings?.modeling?.defaultUnit).toBe('in') | ||||
|       expect(storedSettings.settings?.modeling?.mouseControls).toBe('KittyCAD') | ||||
|       expect(storedSettings.settings?.app?.projectDirectory).toBe('') | ||||
|       expect(storedSettings.settings?.projects?.defaultProjectName).toBe( | ||||
|         'project-$nnn' | ||||
|       ) | ||||
|     ) as { settings: SaveSettingsPayload } | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|     expect(storedSettings.settings?.app?.theme).toBe(undefined) | ||||
|   // The behavior is actually broken. Parent always takes precedence | ||||
|   test.fixme( | ||||
|     'Project settings can be set and override user settings', | ||||
|     async ({ page, homePage }) => { | ||||
|       const u = await getUtils(page) | ||||
|       await test.step(`Setup`, async () => { | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|         await homePage.goToModelingScene() | ||||
|         await page | ||||
|           .getByRole('button', { name: 'Start Sketch' }) | ||||
|           .waitFor({ state: 'visible' }) | ||||
|       }) | ||||
|  | ||||
|     // Check that the invalid settings were removed | ||||
|     expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined) | ||||
|     expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined) | ||||
|     expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined) | ||||
|     expect(storedSettings.settings?.projects?.defaultProjectName).toBe( | ||||
|       undefined | ||||
|     ) | ||||
|   }) | ||||
|       // Selectors and constants | ||||
|       const paneButtonLocator = page.getByTestId('debug-pane-button') | ||||
|       const headingLocator = page.getByRole('heading', { | ||||
|         name: 'Settings', | ||||
|         exact: true, | ||||
|       }) | ||||
|       const inputLocator = page.locator('input[name="modeling-showDebugPanel"]') | ||||
|  | ||||
|   test('Project settings can be set and override user settings', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await test.step(`Setup`, async () => { | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await test.step('Open settings dialog and set "Show debug panel" to on', async () => { | ||||
|         await page.keyboard.press('ControlOrMeta+,') | ||||
|         await expect(headingLocator).toBeVisible() | ||||
|  | ||||
|         /** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */ | ||||
|         await test.step(`Confirm that this dialog has a solid background`, async () => { | ||||
|           await expect | ||||
|             .poll( | ||||
|               () => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), | ||||
|               { | ||||
|                 timeout: 1000, | ||||
|                 message: | ||||
|                   'Checking for solid background, should not see default plane colors', | ||||
|               } | ||||
|             ) | ||||
|             .toBeLessThan(15) | ||||
|         }) | ||||
|  | ||||
|         await page.locator('#showDebugPanel').getByText('OffOn').click() | ||||
|       }) | ||||
|  | ||||
|       // Close it and open again with keyboard shortcut, while KCL editor is focused | ||||
|       // Put the cursor in the editor | ||||
|       await test.step('Open settings with keyboard shortcut', async () => { | ||||
|         await page.getByTestId('settings-close-button').click() | ||||
|         await page.locator('.cm-content').click() | ||||
|         await page.keyboard.press('ControlOrMeta+,') | ||||
|         await expect(headingLocator).toBeVisible() | ||||
|       }) | ||||
|  | ||||
|       // Verify the toast appeared | ||||
|       await expect( | ||||
|         page.getByText(`Set show debug panel to "false" for this project`) | ||||
|       ).toBeVisible() | ||||
|       await expect( | ||||
|         page.getByText(`Set show debug panel to "false" for this project`) | ||||
|       ).not.toBeVisible() | ||||
|  | ||||
|       // Check that the debug panel button is gone | ||||
|       await expect(paneButtonLocator).not.toBeVisible() | ||||
|  | ||||
|       // Check that the user setting was not changed | ||||
|       await page.getByRole('radio', { name: 'User' }).click() | ||||
|       await expect(inputLocator).toBeChecked() | ||||
|  | ||||
|       // Roll back to default of "off" | ||||
|       await await page | ||||
|         .getByText( | ||||
|           'show debug panelRoll back show debug panelRoll back to match' | ||||
|         ) | ||||
|         .hover() | ||||
|       await page | ||||
|         .getByRole('button', { name: 'Start Sketch' }) | ||||
|         .waitFor({ state: 'visible' }) | ||||
|     }) | ||||
|         .getByRole('button', { | ||||
|           name: 'Roll back show debug panel', | ||||
|         }) | ||||
|         .click() | ||||
|       await expect(inputLocator).not.toBeChecked() | ||||
|  | ||||
|     // Selectors and constants | ||||
|     const paneButtonLocator = page.getByTestId('debug-pane-button') | ||||
|     const headingLocator = page.getByRole('heading', { | ||||
|       name: 'Settings', | ||||
|       exact: true, | ||||
|     }) | ||||
|     const inputLocator = page.locator('input[name="modeling-showDebugPanel"]') | ||||
|  | ||||
|     await test.step('Open settings dialog and set "Show debug panel" to on', async () => { | ||||
|       await page.keyboard.press('ControlOrMeta+Shift+,') | ||||
|       await expect(headingLocator).toBeVisible() | ||||
|  | ||||
|       /** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */ | ||||
|       await test.step(`Confirm that this dialog has a solid background`, async () => { | ||||
|         await expect | ||||
|           .poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), { | ||||
|             timeout: 1000, | ||||
|             message: | ||||
|               'Checking for solid background, should not see default plane colors', | ||||
|           }) | ||||
|           .toBeLessThan(15) | ||||
|       }) | ||||
|  | ||||
|       await page.locator('#showDebugPanel').getByText('OffOn').click() | ||||
|     }) | ||||
|  | ||||
|     // Close it and open again with keyboard shortcut, while KCL editor is focused | ||||
|     // Put the cursor in the editor | ||||
|     await test.step('Open settings with keyboard shortcut', async () => { | ||||
|       await page.getByTestId('settings-close-button').click() | ||||
|       await page.locator('.cm-content').click() | ||||
|       await page.keyboard.press('ControlOrMeta+Shift+,') | ||||
|       await expect(headingLocator).toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     // Verify the toast appeared | ||||
|     await expect( | ||||
|       page.getByText(`Set show debug panel to "false" for this project`) | ||||
|     ).toBeVisible() | ||||
|     // Check that the theme changed | ||||
|     await expect(paneButtonLocator).not.toBeVisible() | ||||
|  | ||||
|     // Check that the user setting was not changed | ||||
|     await page.getByRole('radio', { name: 'User' }).click() | ||||
|     await expect(inputLocator).toBeChecked() | ||||
|  | ||||
|     // Roll back to default of "off" | ||||
|     await await page | ||||
|       .getByText('show debug panelRoll back show debug panelRoll back to match') | ||||
|       .hover() | ||||
|     await page | ||||
|       .getByRole('button', { | ||||
|         name: 'Roll back show debug panel', | ||||
|       }) | ||||
|       .click() | ||||
|     await expect(inputLocator).not.toBeChecked() | ||||
|  | ||||
|     // Check that the project setting did not change | ||||
|     await page.getByRole('radio', { name: 'Project' }).click() | ||||
|     await expect( | ||||
|       page.locator('input[name="modeling-showDebugPanel"]') | ||||
|     ).not.toBeChecked() | ||||
|   }) | ||||
|       // Check that the project setting did not change | ||||
|       await page.getByRole('radio', { name: 'Project' }).click() | ||||
|       await expect( | ||||
|         page.locator('input[name="modeling-showDebugPanel"]') | ||||
|       ).not.toBeChecked() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test('Keybindings display the correct hotkey for Command Palette', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await test.step('Open keybindings settings', async () => { | ||||
|       // Open the settings modal with the browser keyboard shortcut | ||||
|       await page.keyboard.press('ControlOrMeta+Shift+,') | ||||
|       // Open the settings modal with the keyboard shortcut | ||||
|       await page.keyboard.press('ControlOrMeta+,') | ||||
|  | ||||
|       // Go to Keybindings tab. | ||||
|       const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' }) | ||||
| @ -174,11 +162,13 @@ test.describe('Testing settings', () => { | ||||
|     await expect(hotkey).toHaveText(text) | ||||
|   }) | ||||
|  | ||||
|   test('Project and user settings can be reset', async ({ page }) => { | ||||
|   test('Project and user settings can be reset', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await test.step(`Setup`, async () => { | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.waitForPageLoad() | ||||
|       await page.waitForTimeout(1000) | ||||
|     }) | ||||
|  | ||||
|     // Selectors and constants | ||||
| @ -261,29 +251,24 @@ test.describe('Testing settings', () => { | ||||
|   test.fixme( | ||||
|     `Project settings override user settings on desktop`, | ||||
|     { tag: ['@electron', '@skipWin'] }, | ||||
|     async ({ browser: _ }, testInfo) => { | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       test.skip( | ||||
|         process.platform === 'win32', | ||||
|         'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557' | ||||
|       ) | ||||
|       const projectName = 'bracket' | ||||
|       const { | ||||
|         electronApp, | ||||
|         page, | ||||
|         dir: projectDirName, | ||||
|       } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async (dir) => { | ||||
|       const { dir: projectDirName } = await context.folderSetupFn( | ||||
|         async (dir) => { | ||||
|           const bracketDir = join(dir, projectName) | ||||
|           await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|           await fsp.copyFile( | ||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||
|             join(bracketDir, 'main.kcl') | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|         } | ||||
|       ) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       // Selectors and constants | ||||
|       const tempProjectSettingsFilePath = join( | ||||
| @ -353,22 +338,18 @@ test.describe('Testing settings', () => { | ||||
|         await logoLink.click() | ||||
|         await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     `Load desktop app with no settings file`, | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browser: _ }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         // This is what makes no settings file get created | ||||
|         cleanProjectDir: false, | ||||
|         testInfo, | ||||
|       }) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     { | ||||
|       tag: '@electron', | ||||
|       // This is what makes no settings file get created | ||||
|       cleanProjectDir: false, | ||||
|     }, | ||||
|     async ({ page }, testInfo) => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       // Selectors and constants | ||||
|       const errorHeading = page.getByRole('heading', { | ||||
| @ -379,25 +360,21 @@ test.describe('Testing settings', () => { | ||||
|       // If the app loads without exploding we're in the clear | ||||
|       await expect(errorHeading).not.toBeVisible() | ||||
|       await expect(projectDirLink).toBeVisible() | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     `Load desktop app with a settings file, but no project directory setting`, | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browser: _ }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         appSettings: { | ||||
|           app: { | ||||
|             themeColor: '259', | ||||
|           }, | ||||
|     { | ||||
|       tag: '@electron', | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           themeColor: '259', | ||||
|         }, | ||||
|       }) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       }, | ||||
|     }, | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       // Selectors and constants | ||||
|       const errorHeading = page.getByRole('heading', { | ||||
| @ -408,32 +385,28 @@ test.describe('Testing settings', () => { | ||||
|       // If the app loads without exploding we're in the clear | ||||
|       await expect(errorHeading).not.toBeVisible() | ||||
|       await expect(projectDirLink).toBeVisible() | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   // It was much easier to test the logo color than the background stream color. | ||||
|   test.fixme( | ||||
|     'user settings reload on external change, on project and modeling view', | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName }, testInfo) => { | ||||
|       const { | ||||
|         electronApp, | ||||
|         page, | ||||
|         dir: projectDirName, | ||||
|       } = await setupElectron({ | ||||
|         testInfo, | ||||
|         appSettings: { | ||||
|           app: { | ||||
|             // Doesn't matter what you set it to. It will | ||||
|             // default to 264.5 | ||||
|             themeColor: '0', | ||||
|           }, | ||||
|     { | ||||
|       tag: '@electron', | ||||
|       appSettings: { | ||||
|         app: { | ||||
|           // Doesn't matter what you set it to. It will | ||||
|           // default to 264.5 | ||||
|           themeColor: '0', | ||||
|         }, | ||||
|       }) | ||||
|       }, | ||||
|     }, | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       const { dir: projectDirName } = await context.folderSetupFn( | ||||
|         async () => {} | ||||
|       ) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       const logoLink = page.getByTestId('app-logo') | ||||
|       const projectDirLink = page.getByText('Loaded from') | ||||
| @ -467,23 +440,18 @@ test.describe('Testing settings', () => { | ||||
|         await changeColor('21') | ||||
|         await expect(logoLink).toHaveCSS('--primary-hue', '21') | ||||
|       }) | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|   test.fixme( | ||||
|     'project settings reload on external change', | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browserName: _ }, testInfo) => { | ||||
|       const { | ||||
|         electronApp, | ||||
|         page, | ||||
|         dir: projectDirName, | ||||
|       } = await setupElectron({ | ||||
|         testInfo, | ||||
|       }) | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       const { dir: projectDirName } = await context.folderSetupFn( | ||||
|         async () => {} | ||||
|       ) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|       const logoLink = page.getByTestId('app-logo') | ||||
|       const projectDirLink = page.getByText('Loaded from') | ||||
| @ -514,29 +482,24 @@ test.describe('Testing settings', () => { | ||||
|         await changeColorFs('99') | ||||
|         await expect(logoLink).toHaveCSS('--primary-hue', '99') | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     `Closing settings modal should go back to the original file being viewed`, | ||||
|     { tag: '@electron' }, | ||||
|     async ({ browser: _ }, testInfo) => { | ||||
|       const { electronApp, page } = await setupElectron({ | ||||
|         testInfo, | ||||
|         folderSetupFn: async (dir) => { | ||||
|           const bracketDir = join(dir, 'project-000') | ||||
|           await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|           await fsp.copyFile( | ||||
|             executorInputPath('cube.kcl'), | ||||
|             join(bracketDir, 'main.kcl') | ||||
|           ) | ||||
|           await fsp.copyFile( | ||||
|             executorInputPath('cylinder.kcl'), | ||||
|             join(bracketDir, '2.kcl') | ||||
|           ) | ||||
|         }, | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       await context.folderSetupFn(async (dir) => { | ||||
|         const bracketDir = join(dir, 'project-000') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('cube.kcl'), | ||||
|           join(bracketDir, 'main.kcl') | ||||
|         ) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('cylinder.kcl'), | ||||
|           join(bracketDir, '2.kcl') | ||||
|         ) | ||||
|       }) | ||||
|       const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8') | ||||
|       const kclCylinder = await fsp.readFile( | ||||
| @ -552,7 +515,7 @@ test.describe('Testing settings', () => { | ||||
|         editorTextMatches, | ||||
|       } = await getUtils(page, test) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       page.on('console', console.log) | ||||
|  | ||||
|       await test.step('Precondition: Open to second project file', async () => { | ||||
| @ -583,16 +546,15 @@ test.describe('Testing settings', () => { | ||||
|       await test.step('Postcondition: Same file content is in editor as before settings opened', async () => { | ||||
|         await editorTextMatches(kclCylinder) | ||||
|       }) | ||||
|  | ||||
|       await electronApp.close() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test('Changing modeling default unit', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|   test('Changing modeling default unit', async ({ page, homePage }) => { | ||||
|     await test.step(`Test setup`, async () => { | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|       const toastMessage = page.getByText(`Successfully created "testDefault"`) | ||||
|       await expect(toastMessage).not.toBeVisible() | ||||
|       await page | ||||
|         .getByRole('button', { name: 'Start Sketch' }) | ||||
|         .waitFor({ state: 'visible' }) | ||||
| @ -619,7 +581,9 @@ test.describe('Testing settings', () => { | ||||
|       await userSettingsTab.click() | ||||
|       await defaultUnitSection.hover() | ||||
|       await defaultUnitRollbackButton.click() | ||||
|       await projectSettingsTab.hover() | ||||
|       await projectSettingsTab.click() | ||||
|       await page.waitForTimeout(1000) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Change modeling default unit within project tab', async () => { | ||||
| @ -631,7 +595,10 @@ test.describe('Testing settings', () => { | ||||
|           const toastMessage = page.getByText( | ||||
|             `Set default unit to "${unitOfMeasure}" for this project` | ||||
|           ) | ||||
|  | ||||
|           // Assert visibility and disapperance | ||||
|           await expect(toastMessage).toBeVisible() | ||||
|           await expect(toastMessage).not.toBeVisible() | ||||
|         }) | ||||
|       } | ||||
|       await changeUnitOfMeasureInProjectTab('in') | ||||
| @ -643,7 +610,10 @@ test.describe('Testing settings', () => { | ||||
|     }) | ||||
|  | ||||
|     // Go to the user tab | ||||
|     await userSettingsTab.hover() | ||||
|     await userSettingsTab.click() | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     await test.step('Change modeling default unit within user tab', async () => { | ||||
|       const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => { | ||||
|         await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => { | ||||
| @ -726,23 +696,26 @@ test.describe('Testing settings', () => { | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test('Changing theme in sketch mode', async ({ page }) => { | ||||
|   test('Changing theme in sketch mode', async ({ context, page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(() => { | ||||
|     await context.addInitScript(() => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([5, 0], %) | ||||
|   |> line([0, 5], %) | ||||
|   |> line([-5, 0], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| extrude001 = extrude(5, sketch001) | ||||
| ` | ||||
|     |> startProfileAt([0, 0], %) | ||||
|     |> line([5, 0], %) | ||||
|     |> line([0, 5], %) | ||||
|     |> line([-5, 0], %) | ||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|     |> close(%) | ||||
|   extrude001 = extrude(5, sketch001) | ||||
|   ` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     // Selectors and constants | ||||
|     const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' }) | ||||
| @ -753,7 +726,6 @@ extrude001 = extrude(5, sketch001) | ||||
|     const lightThemeSegmentColor: [number, number, number] = [90, 90, 90] | ||||
|  | ||||
|     await test.step(`Get into sketch mode`, async () => { | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.mouse.click(700, 200) | ||||
|       await expect(editSketchButton).toBeVisible() | ||||
|       await editSketchButton.click() | ||||
| @ -792,135 +764,125 @@ extrude001 = extrude(5, sketch001) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test(`Changing system theme preferences (via media query) should update UI and stream`, async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     // Override the settings so that the theme is set to `system` | ||||
|     await page.addInitScript( | ||||
|       ({ settingsKey, settings }) => { | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ | ||||
|           settings: TEST_SETTINGS_DEFAULT_THEME, | ||||
|         }), | ||||
|   test( | ||||
|     `Changing system theme preferences (via media query) should update UI and stream`, | ||||
|     { | ||||
|       // Override the settings so that the theme is set to `system` | ||||
|       appSettings: TEST_SETTINGS_DEFAULT_THEME, | ||||
|     }, | ||||
|     async ({ page, homePage }) => { | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
|       // Selectors and constants | ||||
|       const darkBackgroundCss = 'oklch(0.3012 0 264.5)' | ||||
|       const lightBackgroundCss = 'oklch(0.9911 0 264.5)' | ||||
|       const darkBackgroundColor: [number, number, number] = [27, 27, 27] | ||||
|       const lightBackgroundColor: [number, number, number] = [245, 245, 245] | ||||
|       const streamBackgroundPixelIsColor = async ( | ||||
|         color: [number, number, number] | ||||
|       ) => { | ||||
|         return u.getGreatestPixDiff({ x: 1000, y: 200 }, color) | ||||
|       } | ||||
|     ) | ||||
|     const u = await getUtils(page) | ||||
|       const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' }) | ||||
|  | ||||
|     // Selectors and constants | ||||
|     const darkBackgroundCss = 'oklch(0.3012 0 264.5)' | ||||
|     const lightBackgroundCss = 'oklch(0.9911 0 264.5)' | ||||
|     const darkBackgroundColor: [number, number, number] = [27, 27, 27] | ||||
|     const lightBackgroundColor: [number, number, number] = [245, 245, 245] | ||||
|     const streamBackgroundPixelIsColor = async ( | ||||
|       color: [number, number, number] | ||||
|     ) => { | ||||
|       return u.getGreatestPixDiff({ x: 1000, y: 200 }, color) | ||||
|       await test.step(`Test setup`, async () => { | ||||
|         await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|         await homePage.goToModelingScene() | ||||
|         await u.waitForPageLoad() | ||||
|         await page.waitForTimeout(1000) | ||||
|         await expect(toolbar).toBeVisible() | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Check the background color is light before`, async () => { | ||||
|         await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss) | ||||
|         await expect | ||||
|           .poll(() => streamBackgroundPixelIsColor(lightBackgroundColor)) | ||||
|           .toBeLessThan(15) | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => { | ||||
|         await page.emulateMedia({ colorScheme: 'dark' }) | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Check the background color is dark after`, async () => { | ||||
|         await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss) | ||||
|         await expect | ||||
|           .poll(() => streamBackgroundPixelIsColor(darkBackgroundColor)) | ||||
|           .toBeLessThan(15) | ||||
|       }) | ||||
|     } | ||||
|     const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' }) | ||||
|   ) | ||||
|  | ||||
|     await test.step(`Test setup`, async () => { | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await expect(toolbar).toBeVisible() | ||||
|     }) | ||||
|   test( | ||||
|     `Turning off "Show debug panel" with debug panel open leaves no phantom panel`, | ||||
|     { | ||||
|       // Override beforeEach test setup | ||||
|       // with debug panel open | ||||
|       // but "show debug panel" set to false | ||||
|       appSettings: { | ||||
|         ...TEST_SETTINGS, | ||||
|         modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false }, | ||||
|       }, | ||||
|     }, | ||||
|     async ({ context, page, homePage }) => { | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
|     await test.step(`Check the background color is light before`, async () => { | ||||
|       await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss) | ||||
|       await expect | ||||
|         .poll(() => streamBackgroundPixelIsColor(lightBackgroundColor)) | ||||
|         .toBeLessThan(15) | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => { | ||||
|       await page.emulateMedia({ colorScheme: 'dark' }) | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Check the background color is dark after`, async () => { | ||||
|       await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss) | ||||
|       await expect | ||||
|         .poll(() => streamBackgroundPixelIsColor(darkBackgroundColor)) | ||||
|         .toBeLessThan(15) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Override beforeEach test setup | ||||
|     // with debug panel open | ||||
|     // but "show debug panel" set to false | ||||
|     await page.addInitScript( | ||||
|       async ({ settingsKey, settings }) => { | ||||
|         localStorage.setItem(settingsKey, settings) | ||||
|       await context.addInitScript(async () => { | ||||
|         localStorage.setItem( | ||||
|           'persistModelingContext', | ||||
|           '{"openPanes":["debug"]}' | ||||
|         ) | ||||
|       }, | ||||
|       { | ||||
|         settingsKey: TEST_SETTINGS_KEY, | ||||
|         settings: TOML.stringify({ | ||||
|           settings: { | ||||
|             ...TEST_SETTINGS, | ||||
|             modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false }, | ||||
|           }, | ||||
|         }), | ||||
|       }) | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       // Constants and locators | ||||
|       const resizeHandle = page.locator('.sidebar-resize-handles > div.block') | ||||
|       const debugPaneButton = page.getByTestId('debug-pane-button') | ||||
|       const commandsButton = page.getByRole('button', { name: 'Commands' }) | ||||
|       const debugPaneOption = page.getByRole('option', { | ||||
|         name: 'Settings · modeling · show debug panel', | ||||
|       }) | ||||
|  | ||||
|       async function setShowDebugPanelTo(value: 'On' | 'Off') { | ||||
|         await commandsButton.click() | ||||
|         await debugPaneOption.click() | ||||
|         await page.getByRole('option', { name: value }).click() | ||||
|         await expect( | ||||
|           page.getByText( | ||||
|             `Set show debug panel to "${value === 'On'}" for this project` | ||||
|           ) | ||||
|         ).toBeVisible() | ||||
|       } | ||||
|     ) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     // Constants and locators | ||||
|     const resizeHandle = page.locator('.sidebar-resize-handles > div.block') | ||||
|     const debugPaneButton = page.getByTestId('debug-pane-button') | ||||
|     const commandsButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     const debugPaneOption = page.getByRole('option', { | ||||
|       name: 'Settings · modeling · show debug panel', | ||||
|     }) | ||||
|       await test.step(`Initial load with corrupted settings`, async () => { | ||||
|         // Check that the debug panel is not visible | ||||
|         await expect(debugPaneButton).not.toBeVisible() | ||||
|         // Check the pane resize handle wrapper is not visible | ||||
|         await expect(resizeHandle).not.toBeVisible() | ||||
|       }) | ||||
|  | ||||
|     async function setShowDebugPanelTo(value: 'On' | 'Off') { | ||||
|       await commandsButton.click() | ||||
|       await debugPaneOption.click() | ||||
|       await page.getByRole('option', { name: value }).click() | ||||
|       await expect( | ||||
|         page.getByText( | ||||
|           `Set show debug panel to "${value === 'On'}" for this project` | ||||
|         ) | ||||
|       ).toBeVisible() | ||||
|       await test.step(`Open code pane to verify we see the resize handles`, async () => { | ||||
|         await u.openKclCodePanel() | ||||
|         await expect(resizeHandle).toBeVisible() | ||||
|         await u.closeKclCodePanel() | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Turn on debug panel, open it`, async () => { | ||||
|         await setShowDebugPanelTo('On') | ||||
|         await expect(debugPaneButton).toBeVisible() | ||||
|         // We want the logic to clear the phantom panel, so we shouldn't see | ||||
|         // the real panel (and therefore the resize handle) yet | ||||
|         await expect(resizeHandle).not.toBeVisible() | ||||
|         await u.openDebugPanel() | ||||
|         await expect(resizeHandle).toBeVisible() | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Turn off debug panel setting with it open`, async () => { | ||||
|         await setShowDebugPanelTo('Off') | ||||
|         await expect(debugPaneButton).not.toBeVisible() | ||||
|         await expect(resizeHandle).not.toBeVisible() | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     await test.step(`Initial load with corrupted settings`, async () => { | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       // Check that the debug panel is not visible | ||||
|       await expect(debugPaneButton).not.toBeVisible() | ||||
|       // Check the pane resize handle wrapper is not visible | ||||
|       await expect(resizeHandle).not.toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Open code pane to verify we see the resize handles`, async () => { | ||||
|       await u.openKclCodePanel() | ||||
|       await expect(resizeHandle).toBeVisible() | ||||
|       await u.closeKclCodePanel() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Turn on debug panel, open it`, async () => { | ||||
|       await setShowDebugPanelTo('On') | ||||
|       await expect(debugPaneButton).toBeVisible() | ||||
|       // We want the logic to clear the phantom panel, so we shouldn't see | ||||
|       // the real panel (and therefore the resize handle) yet | ||||
|       await expect(resizeHandle).not.toBeVisible() | ||||
|       await u.openDebugPanel() | ||||
|       await expect(resizeHandle).toBeVisible() | ||||
|     }) | ||||
|  | ||||
|     await test.step(`Turn off debug panel setting with it open`, async () => { | ||||
|       await setShowDebugPanelTo('Off') | ||||
|       await expect(debugPaneButton).not.toBeVisible() | ||||
|       await expect(resizeHandle).not.toBeVisible() | ||||
|     }) | ||||
|   }) | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| @ -1,29 +1,16 @@ | ||||
| import { test, expect, Page } from '@playwright/test' | ||||
| import { | ||||
|   getUtils, | ||||
|   setup, | ||||
|   tearDown, | ||||
|   setupElectron, | ||||
|   createProject, | ||||
| } from './test-utils' | ||||
| import { test, expect, Page } from './zoo-test' | ||||
| import { getUtils, createProject } from './test-utils' | ||||
| import { join } from 'path' | ||||
| import fs from 'fs' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.describe('Text-to-CAD tests', () => { | ||||
|   test('basic lego happy case', async ({ page }) => { | ||||
|   test('basic lego happy case', async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await test.step('Set up', async () => { | ||||
|       await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.waitForPageLoad() | ||||
|     }) | ||||
|  | ||||
|     await sendPromptFromCommandBar(page, 'a 2x4 lego') | ||||
| @ -43,25 +30,17 @@ test.describe('Text-to-CAD tests', () => { | ||||
|     const successToastMessage = page.getByText(`Text-to-CAD successful`) | ||||
|     await expect(successToastMessage).toBeVisible({ timeout: 15000 }) | ||||
|  | ||||
|     await expect(page.getByText('Copied')).not.toBeVisible() | ||||
|  | ||||
|     // Hit copy to clipboard. | ||||
|     // Hit accept. | ||||
|     const copyToClipboardButton = page.getByRole('button', { | ||||
|       name: 'Copy to clipboard', | ||||
|       name: 'Accept', | ||||
|     }) | ||||
|     await expect(copyToClipboardButton).toBeVisible() | ||||
|  | ||||
|     await copyToClipboardButton.click() | ||||
|  | ||||
|     // Expect the code to be copied. | ||||
|     await expect(page.getByText('Copied')).toBeVisible() | ||||
|  | ||||
|     // Click in the code editor. | ||||
|     await page.locator('.cm-content').click() | ||||
|  | ||||
|     // Paste the code. | ||||
|     await page.keyboard.press('ControlOrMeta+KeyV') | ||||
|  | ||||
|     // Expect the code to be pasted. | ||||
|     await expect(page.locator('.cm-content')).toContainText(`const`) | ||||
|  | ||||
| @ -70,29 +49,18 @@ test.describe('Text-to-CAD tests', () => { | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // Find the toast close button. | ||||
|     const closeButton = page | ||||
|       .getByRole('status') | ||||
|       .locator('div') | ||||
|       .filter({ hasText: 'Text-to-CAD successfulPrompt' }) | ||||
|       .first() | ||||
|       .getByRole('button', { name: 'Close' }) | ||||
|     await expect(closeButton).toBeVisible() | ||||
|     await closeButton.click() | ||||
|  | ||||
|     // The toast should disappear. | ||||
|     await expect(successToastMessage).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('success model, then ignore success toast, user can create new prompt from command bar', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await sendPromptFromCommandBar(page, 'a 2x6 lego') | ||||
|  | ||||
| @ -111,10 +79,6 @@ test.describe('Text-to-CAD tests', () => { | ||||
|     const successToastMessage = page.getByText(`Text-to-CAD successful`) | ||||
|     await expect(successToastMessage).toBeVisible({ timeout: 15000 }) | ||||
|  | ||||
|     await expect(page.getByText('Copied')).not.toBeVisible() | ||||
|  | ||||
|     await expect(successToastMessage).toBeVisible() | ||||
|  | ||||
|     // Can send a new prompt from the command bar. | ||||
|     await sendPromptFromCommandBar(page, 'a 2x4 lego') | ||||
|  | ||||
| @ -133,12 +97,14 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|   test('you can reject text-to-cad output and it does nothing', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await sendPromptFromCommandBar(page, 'a 2x4 lego') | ||||
|  | ||||
| @ -170,12 +136,16 @@ test.describe('Text-to-CAD tests', () => { | ||||
|     await expect(page.locator('.cm-content')).toContainText(``) | ||||
|   }) | ||||
|  | ||||
|   test('sending a bad prompt fails, can dismiss', async ({ page }) => { | ||||
|   test('sending a bad prompt fails, can dismiss', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     await expect(commandBarButton).toBeVisible() | ||||
| @ -236,12 +206,14 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|   test('sending a bad prompt fails, can start over from toast', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     await expect(commandBarButton).toBeVisible() | ||||
| @ -324,12 +296,14 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|   test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     await expect(commandBarButton).toBeVisible() | ||||
| @ -391,19 +365,21 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|     await expect(successToastMessage).toBeVisible({ timeout: 15000 }) | ||||
|  | ||||
|     await expect(page.getByText('Copied')).not.toBeVisible() | ||||
|  | ||||
|     // old failure toast should stick around. | ||||
|     await expect(failureToastMessage).toBeVisible() | ||||
|     await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('ensure you can shift+enter in the prompt box', async ({ page }) => { | ||||
|   test('ensure you can shift+enter in the prompt box', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     const promptWithNewline = `a 2x4\nlego` | ||||
|  | ||||
| @ -456,7 +432,7 @@ test.describe('Text-to-CAD tests', () => { | ||||
|   test( | ||||
|     'can do many at once and get many prompts back, and interact with many', | ||||
|     { tag: ['@skipWin'] }, | ||||
|     async ({ page }) => { | ||||
|     async ({ page, homePage }) => { | ||||
|       // Let this test run longer since we've seen it timeout. | ||||
|       test.setTimeout(180_000) | ||||
|       // skip on windows | ||||
| @ -467,9 +443,10 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
|       await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|       await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.waitForPageLoad() | ||||
|  | ||||
|       await sendPromptFromCommandBar(page, 'a 2x4 lego') | ||||
|  | ||||
| @ -495,8 +472,6 @@ test.describe('Text-to-CAD tests', () => { | ||||
|       // We should have three success toasts. | ||||
|       await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 }) | ||||
|  | ||||
|       await expect(page.getByText('Copied')).not.toBeVisible() | ||||
|  | ||||
|       await expect(page.getByText(`a 2x4 lego`)).toBeVisible() | ||||
|       await expect(page.getByText(`a 2x8 lego`)).toBeVisible() | ||||
|       await expect(page.getByText(`a 2x10 lego`)).toBeVisible() | ||||
| @ -514,31 +489,15 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|       // Ensure you can copy the code for one of the models remaining. | ||||
|       const copyToClipboardButton = page.getByRole('button', { | ||||
|         name: 'Copy to clipboard', | ||||
|         name: 'Accept', | ||||
|       }) | ||||
|       await expect(copyToClipboardButton.first()).toBeVisible() | ||||
|       // Click the button. | ||||
|       await copyToClipboardButton.first().click() | ||||
|  | ||||
|       // Expect the code to be copied. | ||||
|       await expect(page.getByText('Copied')).toBeVisible() | ||||
|  | ||||
|       // Click in the code editor. | ||||
|       await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) | ||||
|  | ||||
|       // Paste the code. | ||||
|       await page.keyboard.down('ControlOrMeta') | ||||
|       await page.keyboard.press('KeyV') | ||||
|       await page.keyboard.up('ControlOrMeta') | ||||
|  | ||||
|       // Expect the code to be pasted. | ||||
|       await expect(page.locator('.cm-content')).toContainText(`2x8`) | ||||
|  | ||||
|       // Find the toast close button. | ||||
|       const closeButton = page.locator('[data-negative-button="close"]').first() | ||||
|       await expect(closeButton).toBeVisible() | ||||
|       await closeButton.click() | ||||
|  | ||||
|       // Ensure the final toast remains. | ||||
|       await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() | ||||
|       await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible() | ||||
| @ -549,40 +508,21 @@ test.describe('Text-to-CAD tests', () => { | ||||
|       // Click the button. | ||||
|       await copyToClipboardButton.click() | ||||
|  | ||||
|       // Expect the code to be copied. | ||||
|       await expect(page.getByText('Copied')).toBeVisible() | ||||
|  | ||||
|       // Click in the code editor. | ||||
|       await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) | ||||
|  | ||||
|       // Paste the code. | ||||
|       await page.keyboard.down('ControlOrMeta') | ||||
|       await page.keyboard.press('KeyA') | ||||
|       await page.keyboard.up('ControlOrMeta') | ||||
|       await page.keyboard.press('Backspace') | ||||
|       await page.keyboard.down('ControlOrMeta') | ||||
|       await page.keyboard.press('KeyV') | ||||
|       await page.keyboard.up('ControlOrMeta') | ||||
|  | ||||
|       // Expect the code to be pasted. | ||||
|       await expect(page.locator('.cm-content')).toContainText(`2x4`) | ||||
|  | ||||
|       // Expect the toast to disappear. | ||||
|       // Find the toast close button. | ||||
|       await expect(closeButton).toBeVisible() | ||||
|       await closeButton.click() | ||||
|       await expect(successToastMessage).not.toBeVisible() | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await homePage.goToModelingScene() | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await sendPromptFromCommandBar(page, 'a 2x4 lego') | ||||
|  | ||||
| @ -631,57 +571,37 @@ test.describe('Text-to-CAD tests', () => { | ||||
|  | ||||
|     // Ensure you can copy the code for one of the models remaining. | ||||
|     const copyToClipboardButton = page.getByRole('button', { | ||||
|       name: 'Copy to clipboard', | ||||
|       name: 'Accept', | ||||
|     }) | ||||
|     await expect(copyToClipboardButton.first()).toBeVisible() | ||||
|     // Click the button. | ||||
|     await copyToClipboardButton.first().click() | ||||
|  | ||||
|     // Expect the code to be copied. | ||||
|     await expect(page.getByText('Copied')).toBeVisible() | ||||
|  | ||||
|     // Click in the code editor. | ||||
|     await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) | ||||
|  | ||||
|     // Paste the code. | ||||
|     await page.keyboard.down('ControlOrMeta') | ||||
|     await page.keyboard.press('KeyV') | ||||
|     await page.keyboard.up('ControlOrMeta') | ||||
|  | ||||
|     // Expect the code to be pasted. | ||||
|     await expect(page.locator('.cm-content')).toContainText(`2x4`) | ||||
|  | ||||
|     // Find the toast close button. | ||||
|     const closeButton = page | ||||
|       .getByRole('status') | ||||
|       .locator('div') | ||||
|       .filter({ hasText: 'Text-to-CAD successfulPrompt' }) | ||||
|       .first() | ||||
|       .getByRole('button', { name: 'Close' }) | ||||
|     await expect(closeButton).toBeVisible() | ||||
|     await closeButton.click() | ||||
|  | ||||
|     // Expect the toast to disappear. | ||||
|     await expect(page.getByText('Copied')).not.toBeVisible() | ||||
|     await expect(successToastMessage).not.toBeVisible() | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| async function sendPromptFromCommandBar(page: Page, promptStr: string) { | ||||
|   await page.waitForTimeout(1000) | ||||
|   await test.step(`Send prompt from command bar: ${promptStr}`, async () => { | ||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     await expect(commandBarButton).toBeVisible() | ||||
|     // Click the command bar button | ||||
|     await commandBarButton.hover() | ||||
|     await commandBarButton.click() | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     // Wait for the command bar to appear | ||||
|     const cmdSearchBar = page.getByPlaceholder('Search commands') | ||||
|     await expect(cmdSearchBar).toBeVisible() | ||||
|  | ||||
|     const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API ') | ||||
|     const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API') | ||||
|     await expect(textToCadCommand.first()).toBeVisible() | ||||
|     // Click the Text-to-CAD command | ||||
|     await textToCadCommand.first().scrollIntoViewIfNeeded() | ||||
|     await textToCadCommand.first().click() | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     // Enter the prompt. | ||||
|     const prompt = page.getByText('Prompt') | ||||
| @ -697,12 +617,13 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) { | ||||
| test( | ||||
|   'Text-to-CAD functionality', | ||||
|   { tag: '@electron' }, | ||||
|   async ({ browserName }, testInfo) => { | ||||
|   async ({ context, page }, testInfo) => { | ||||
|     const projectName = 'project-000' | ||||
|     const prompt = 'lego 2x4' | ||||
|     const textToCadFileName = 'lego-2x4.kcl' | ||||
|  | ||||
|     const { electronApp, page, dir } = await setupElectron({ testInfo }) | ||||
|     const { dir } = await context.folderSetupFn(async () => {}) | ||||
|  | ||||
|     const fileExists = () => | ||||
|       fs.existsSync(join(dir, projectName, textToCadFileName)) | ||||
|  | ||||
| @ -711,7 +632,7 @@ test( | ||||
|       test | ||||
|     ) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|     // Locators | ||||
|     const projectMenuButton = page | ||||
| @ -761,7 +682,5 @@ test( | ||||
|       // Confirm we've navigated back to the main.kcl file after deletion | ||||
|       await expect(projectMenuButton).toContainText('main.kcl') | ||||
|     }) | ||||
|  | ||||
|     await electronApp.close() | ||||
|   } | ||||
| ) | ||||
|  | ||||
| @ -1,19 +1,10 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { test, expect } from './zoo-test' | ||||
|  | ||||
| import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils' | ||||
| import { doExport, getUtils, makeTemplate } from './test-utils' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }, testInfo) => { | ||||
|   await setup(context, page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test('Units menu', async ({ page }) => { | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await u.waitForAuthSkipAppStart() | ||||
| test.fixme('Units menu', async ({ page, homePage }) => { | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   const unitsMenuButton = page.getByRole('button', { | ||||
|     name: 'Current Units', | ||||
| @ -41,7 +32,7 @@ test('Units menu', async ({ page }) => { | ||||
|   await expect(unitsMenuButton).toContainText('mm') | ||||
| }) | ||||
|  | ||||
| test('Successful export shows a success toast', async ({ page }) => { | ||||
| test('Successful export shows a success toast', async ({ page, homePage }) => { | ||||
|   // FYI this test doesn't work with only engine running locally | ||||
|   // And you will need to have the KittyCAD CLI installed | ||||
|   const u = await getUtils(page) | ||||
| @ -57,41 +48,41 @@ totalHeightHalf = 2 | ||||
| armThick = 0.5 | ||||
| totalLen = 9.5 | ||||
| part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> yLine(baseHeight, %) | ||||
|   |> xLine(baseLen, %) | ||||
|   |> angledLineToY({ | ||||
|         angle: topAng, | ||||
|         to: totalHeightHalf, | ||||
|       }, %, $seg04) | ||||
|   |> xLineTo(totalLen, %, $seg03) | ||||
|   |> yLine(-armThick, %, $seg01) | ||||
|   |> angledLineThatIntersects({ | ||||
|         angle: HALF_TURN, | ||||
|         offset: -armThick, | ||||
|         intersectTag: seg04 | ||||
|       }, %) | ||||
|   |> angledLineToY([segAng(seg04) + 180, ZERO], %) | ||||
|   |> angledLineToY({ | ||||
|         angle: -bottomAng, | ||||
|         to: -totalHeightHalf - armThick, | ||||
|       }, %, $seg02) | ||||
|   |> xLineTo(segEndX(seg03) + 0, %) | ||||
|   |> yLine(-segLen(seg01), %) | ||||
|   |> angledLineThatIntersects({ | ||||
|         angle: HALF_TURN, | ||||
|         offset: -armThick, | ||||
|         intersectTag: seg02 | ||||
|       }, %) | ||||
|   |> angledLineToY([segAng(seg02) + 180, -baseHeight], %) | ||||
|   |> xLineTo(ZERO, %) | ||||
|   |> close(%) | ||||
|   |> extrude(4, %)` | ||||
| |> startProfileAt([0, 0], %) | ||||
| |> yLine(baseHeight, %) | ||||
| |> xLine(baseLen, %) | ||||
| |> angledLineToY({ | ||||
|       angle = topAng, | ||||
|       to = totalHeightHalf, | ||||
|     }, %, $seg04) | ||||
| |> xLineTo(totalLen, %, $seg03) | ||||
| |> yLine(-armThick, %, $seg01) | ||||
| |> angledLineThatIntersects({ | ||||
|       angle = HALF_TURN, | ||||
|       offset = -armThick, | ||||
|       intersectTag = seg04 | ||||
|     }, %) | ||||
| |> angledLineToY([segAng(seg04) + 180, ZERO], %) | ||||
| |> angledLineToY({ | ||||
|       angle = -bottomAng, | ||||
|       to = -totalHeightHalf - armThick, | ||||
|     }, %, $seg02) | ||||
| |> xLineTo(segEndX(seg03) + 0, %) | ||||
| |> yLine(-segLen(seg01), %) | ||||
| |> angledLineThatIntersects({ | ||||
|       angle = HALF_TURN, | ||||
|       offset = -armThick, | ||||
|       intersectTag = seg02 | ||||
|     }, %) | ||||
| |> angledLineToY([segAng(seg02) + 180, -baseHeight], %) | ||||
| |> xLineTo(ZERO, %) | ||||
| |> close(%) | ||||
| |> extrude(4, %)` | ||||
|     ) | ||||
|   }) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await homePage.goToModelingScene() | ||||
|   await u.openDebugPanel() | ||||
|   await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|   await u.waitForCmdReceive('extrude') | ||||
| @ -106,25 +97,14 @@ part001 = startSketchOn('-XZ') | ||||
|     }, | ||||
|     page | ||||
|   ) | ||||
|  | ||||
|   // This is the main thing we're testing, | ||||
|   // We test the export functionality across all | ||||
|   // file types in snapshot-tests.spec.ts | ||||
|   await expect(page.getByText('Exported successfully')).toBeVisible() | ||||
| }) | ||||
|  | ||||
| test('Paste should not work unless an input is focused', async ({ | ||||
|   page, | ||||
|   browserName, | ||||
|   homePage, | ||||
| }) => { | ||||
|   // To run this test locally, uncomment Firefox in playwright.config.ts | ||||
|   test.skip( | ||||
|     browserName !== 'firefox', | ||||
|     "This bug is really Firefox-only, which we don't run in CI." | ||||
|   ) | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|   await homePage.goToModelingScene() | ||||
|   await page | ||||
|     .getByRole('button', { name: 'Start Sketch' }) | ||||
|     .waitFor({ state: 'visible' }) | ||||
| @ -164,12 +144,12 @@ test('Paste should not work unless an input is focused', async ({ | ||||
|  | ||||
| test('Keyboard shortcuts can be viewed through the help menu', async ({ | ||||
|   page, | ||||
|   homePage, | ||||
| }) => { | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) | ||||
|   await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' }) | ||||
|   await page | ||||
|     .getByRole('button', { name: 'Start Sketch' }) | ||||
|     .waitFor({ state: 'visible' }) | ||||
| @ -181,7 +161,7 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({ | ||||
|   await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click() | ||||
|  | ||||
|   // Verify the URL and that you can see a list of shortcuts | ||||
|   await expect(page.url()).toContain('?tab=keybindings') | ||||
|   await expect.poll(() => page.url()).toContain('?tab=keybindings') | ||||
|   await expect( | ||||
|     page.getByRole('heading', { name: 'Enter Sketch Mode' }) | ||||
|   ).toBeAttached() | ||||
| @ -189,12 +169,13 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({ | ||||
|  | ||||
| test('First escape in tool pops you out of tool, second exits sketch mode', async ({ | ||||
|   page, | ||||
|   homePage, | ||||
| }) => { | ||||
|   // Wait for the app to be ready for use | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await homePage.goToModelingScene() | ||||
|   await u.openDebugPanel() | ||||
|   await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|   await u.closeDebugPanel() | ||||
| @ -258,7 +239,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn | ||||
|  | ||||
| test.fixme( | ||||
|   'Basic default modeling and sketch hotkeys work', | ||||
|   async ({ page }) => { | ||||
|   async ({ page, homePage }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // This test can run long if it takes a little too long to load | ||||
| @ -285,8 +266,8 @@ test.fixme( | ||||
|           }) | ||||
|         ) | ||||
|       }) | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       await homePage.goToModelingScene() | ||||
|       await u.openDebugPanel() | ||||
|       await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|       await u.closeDebugPanel() | ||||
| @ -437,10 +418,11 @@ test.fixme( | ||||
|   } | ||||
| ) | ||||
|  | ||||
| test('Delete key does not navigate back', async ({ page }) => { | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
|   await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) | ||||
| test('Delete key does not navigate back', async ({ page, homePage }) => { | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' }) | ||||
|  | ||||
|   const settingsButton = page.getByRole('link', { | ||||
|     name: 'Settings', | ||||
| @ -449,45 +431,45 @@ test('Delete key does not navigate back', async ({ page }) => { | ||||
|   const settingsCloseButton = page.getByTestId('settings-close-button') | ||||
|  | ||||
|   await settingsButton.click() | ||||
|   await expect(page.url()).toContain('/settings') | ||||
|   await expect.poll(() => page.url()).toContain('/settings') | ||||
|  | ||||
|   // Make sure that delete doesn't go back from settings | ||||
|   await page.keyboard.press('Delete') | ||||
|   await expect(page.url()).toContain('/settings') | ||||
|   await expect.poll(() => page.url()).toContain('/settings') | ||||
|  | ||||
|   // Now close the settings and try delete again, | ||||
|   // make sure it doesn't go back to settings | ||||
|   await settingsCloseButton.click() | ||||
|   await page.keyboard.press('Delete') | ||||
|   await expect(page.url()).not.toContain('/settings') | ||||
|   await expect.poll(() => page.url()).not.toContain('/settings') | ||||
| }) | ||||
|  | ||||
| test('Sketch on face', async ({ page }) => { | ||||
| test('Sketch on face', async ({ page, homePage }) => { | ||||
|   test.setTimeout(90_000) | ||||
|   const u = await getUtils(page) | ||||
|   await page.addInitScript(async () => { | ||||
|     localStorage.setItem( | ||||
|       'persistCode', | ||||
|       `sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([3.29, 7.86], %) | ||||
|   |> line([2.48, 2.44], %) | ||||
|   |> line([2.66, 1.17], %) | ||||
|   |> line([3.75, 0.46], %) | ||||
|   |> line([4.99, -0.46], %) | ||||
|   |> line([3.3, -2.12], %) | ||||
|   |> line([2.16, -3.33], %) | ||||
|   |> line([0.85, -3.08], %) | ||||
|   |> line([-0.18, -3.36], %) | ||||
|   |> line([-3.86, -2.73], %) | ||||
|   |> line([-17.67, 0.85], %) | ||||
|   |> close(%) | ||||
|   extrude001 = extrude(5 + 7, sketch001)` | ||||
| |> startProfileAt([3.29, 7.86], %) | ||||
| |> line([2.48, 2.44], %) | ||||
| |> line([2.66, 1.17], %) | ||||
| |> line([3.75, 0.46], %) | ||||
| |> line([4.99, -0.46], %) | ||||
| |> line([3.3, -2.12], %) | ||||
| |> line([2.16, -3.33], %) | ||||
| |> line([0.85, -3.08], %) | ||||
| |> line([-0.18, -3.36], %) | ||||
| |> line([-3.86, -2.73], %) | ||||
| |> line([-17.67, 0.85], %) | ||||
| |> close(%) | ||||
| extrude001 = extrude(5 + 7, sketch001)` | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|  | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await homePage.goToModelingScene() | ||||
|  | ||||
|   // wait for execution done | ||||
|   await u.openDebugPanel() | ||||
| @ -541,7 +523,8 @@ test('Sketch on face', async ({ page }) => { | ||||
|   |> line([2.45, -0.2], %) | ||||
|   |> line([-2.6, -1.25], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%)`) | ||||
|   |> close(%) | ||||
| `) | ||||
|   ) | ||||
|  | ||||
|   await u.openAndClearDebugPanel() | ||||
| @ -556,7 +539,7 @@ test('Sketch on face', async ({ page }) => { | ||||
|   await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|   await page.waitForTimeout(400) | ||||
|   await page.waitForTimeout(150) | ||||
|   await page.setViewportSize({ width: 1200, height: 1200 }) | ||||
|   await page.setBodyDimensions({ width: 1200, height: 1200 }) | ||||
|   await u.openAndClearDebugPanel() | ||||
|   await u.updateCamPosition([452, -152, 1166]) | ||||
|   await u.closeDebugPanel() | ||||
| @ -574,11 +557,11 @@ test('Sketch on face', async ({ page }) => { | ||||
|   previousCodeContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|   const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01) | ||||
|   |> startProfileAt([-12.83, 6.7], %) | ||||
|   |> line([${[2.28, 2.35]}, -${0.07}], %) | ||||
|   |> line([-3.05, -1.47], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%)` | ||||
| |> startProfileAt([-12.83, 6.7], %) | ||||
| |> line([${[2.28, 2.35]}, -${0.07}], %) | ||||
| |> line([-3.05, -1.47], %) | ||||
| |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
| |> close(%)` | ||||
|  | ||||
|   await expect(page.locator('.cm-content')).toHaveText(result.regExp) | ||||
|  | ||||
| @ -602,6 +585,6 @@ test('Sketch on face', async ({ page }) => { | ||||
|   await page.getByRole('button', { name: 'checkmark Submit command' }).click() | ||||
|  | ||||
|   const result2 = result.genNext` | ||||
|   const sketch002 = extrude(${[5, 5]} + 7, sketch002)` | ||||
| const sketch002 = extrude(${[5, 5]} + 7, sketch002)` | ||||
|   await expect(page.locator('.cm-content')).toHaveText(result2.regExp) | ||||
| }) | ||||
|  | ||||
							
								
								
									
										305
									
								
								e2e/playwright/zoo-test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,305 @@ | ||||
| import { | ||||
|   test as playwrightTestFn, | ||||
|   TestInfo as TestInfoPlaywright, | ||||
|   BrowserContext as BrowserContextPlaywright, | ||||
|   Page as PagePlaywright, | ||||
|   TestDetails as TestDetailsPlaywright, | ||||
|   PlaywrightTestArgs, | ||||
|   PlaywrightTestOptions, | ||||
|   PlaywrightWorkerArgs, | ||||
|   PlaywrightWorkerOptions, | ||||
|   ElectronApplication, | ||||
| } from '@playwright/test' | ||||
|  | ||||
| import { | ||||
|   fixtures, | ||||
|   Fixtures, | ||||
|   AuthenticatedTronApp, | ||||
|   AuthenticatedApp, | ||||
| } from './fixtures/fixtureSetup' | ||||
|  | ||||
| import { SaveSettingsPayload } from 'lib/settings/settingsTypes' | ||||
| export { expect } from '@playwright/test' | ||||
|  | ||||
| declare module '@playwright/test' { | ||||
|   interface TestInfo { | ||||
|     tronApp?: AuthenticatedTronApp | ||||
|   } | ||||
|   interface BrowserContext { | ||||
|     folderSetupFn: ( | ||||
|       cb: (dir: string) => Promise<void> | ||||
|     ) => Promise<{ dir: string }> | ||||
|   } | ||||
|   interface Page { | ||||
|     dir: string | ||||
|     TEST_SETTINGS_FILE_KEY?: string | ||||
|     setBodyDimensions: (dims: { | ||||
|       width: number | ||||
|       height: number | ||||
|     }) => Promise<void> | ||||
|   } | ||||
| } | ||||
|  | ||||
| export type TestInfo = TestInfoPlaywright | ||||
| export type BrowserContext = BrowserContextPlaywright | ||||
| export type Page = PagePlaywright | ||||
| export type TestDetails = TestDetailsPlaywright & { | ||||
|   cleanProjectDir?: boolean | ||||
|   appSettings?: Partial<SaveSettingsPayload> | ||||
| } | ||||
|  | ||||
| // Our custom decorated Zoo test object. Makes it easier to add fixtures, and | ||||
| // switch between web and electron if needed. | ||||
| const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures) | ||||
|  | ||||
| // In JavaScript you cannot replace a function's body only (despite functions | ||||
| // are themselves objects, which you'd expect a body property or something...) | ||||
| // So we must redefine the function and then re-attach properties. | ||||
| type PWFunction = ( | ||||
|   args: PlaywrightTestArgs & | ||||
|     Fixtures & | ||||
|     PlaywrightWorkerArgs & | ||||
|     PlaywrightTestOptions & | ||||
|     PlaywrightWorkerOptions & { | ||||
|       electronApp?: ElectronApplication | ||||
|     }, | ||||
|   testInfo: TestInfo | ||||
| ) => void | Promise<void> | ||||
|  | ||||
| // The below error is due to the extreme type spaghetti going on. playwright/ | ||||
| // types/test.d.ts does not export 2 functions (below is one of them) but tsc | ||||
| // is trying to use a interface name it can't see. | ||||
| // e2e/playwright/zoo-test.ts:64:14 - error TS4023: Exported variable 'test' has | ||||
| // or is using name 'TestFunction' from external module | ||||
| // "/home/lee/Code/Zoo/modeling-app/dirty2/node_modules/playwright/types/test" | ||||
| // but cannot be named. | ||||
| export const test = ( | ||||
|   desc: string, | ||||
|   objOrFn: PWFunction | TestDetails, | ||||
|   fnMaybe?: PWFunction | ||||
| ) => { | ||||
|   const hasTestConf = typeof objOrFn === 'object' | ||||
|   const fn = hasTestConf ? fnMaybe : objOrFn | ||||
|  | ||||
|   return pwTestFnWithFixtures( | ||||
|     desc, | ||||
|     hasTestConf ? objOrFn : {}, | ||||
|     async ( | ||||
|       { | ||||
|         page, | ||||
|         context, | ||||
|         cmdBar, | ||||
|         editor, | ||||
|         toolbar, | ||||
|         scene, | ||||
|         homePage, | ||||
|         request, | ||||
|         playwright, | ||||
|         browser, | ||||
|         acceptDownloads, | ||||
|         bypassCSP, | ||||
|         colorScheme, | ||||
|         clientCertificates, | ||||
|         deviceScaleFactor, | ||||
|         extraHTTPHeaders, | ||||
|         geolocation, | ||||
|         hasTouch, | ||||
|         httpCredentials, | ||||
|         ignoreHTTPSErrors, | ||||
|         isMobile, | ||||
|         javaScriptEnabled, | ||||
|         locale, | ||||
|         offline, | ||||
|         permissions, | ||||
|         proxy, | ||||
|         storageState, | ||||
|         timezoneId, | ||||
|         userAgent, | ||||
|         viewport, | ||||
|         baseURL, | ||||
|         contextOptions, | ||||
|         actionTimeout, | ||||
|         navigationTimeout, | ||||
|         serviceWorkers, | ||||
|         testIdAttribute, | ||||
|         browserName, | ||||
|         defaultBrowserType, | ||||
|         headless, | ||||
|         channel, | ||||
|         launchOptions, | ||||
|         connectOptions, | ||||
|         screenshot, | ||||
|         trace, | ||||
|         video, | ||||
|       }, | ||||
|       testInfo | ||||
|     ) => { | ||||
|       // To switch to web, use PLATFORM=web environment variable. | ||||
|       // Only use this for debugging, since the playwright tracer is busted | ||||
|       // for electron. | ||||
|  | ||||
|       let tronApp | ||||
|  | ||||
|       if (process.env.PLATFORM === 'web') { | ||||
|         tronApp = new AuthenticatedApp(context, page, testInfo) | ||||
|       } else { | ||||
|         tronApp = new AuthenticatedTronApp(context, page, testInfo) | ||||
|       } | ||||
|  | ||||
|       const fixtures: Fixtures = { cmdBar, editor, toolbar, scene, homePage } | ||||
|       if (tronApp instanceof AuthenticatedTronApp) { | ||||
|         const options = { | ||||
|           fixtures, | ||||
|         } | ||||
|         if (hasTestConf) { | ||||
|           Object.assign(options, { | ||||
|             appSettings: objOrFn?.appSettings, | ||||
|             cleanProjectDir: objOrFn?.cleanProjectDir, | ||||
|           }) | ||||
|         } | ||||
|         await tronApp.initialise(options) | ||||
|       } else { | ||||
|         await tronApp.initialise('') | ||||
|       } | ||||
|  | ||||
|       // We need to patch this because addInitScript will bind too late in our | ||||
|       // electron tests, never running. We need to call reload() after each call | ||||
|       // to guarantee it runs. | ||||
|       const oldContextAddInitScript = tronApp.context.addInitScript | ||||
|       tronApp.context.addInitScript = async function (a, b) { | ||||
|         // @ts-ignore pretty sure way out of tsc's type checking capabilities. | ||||
|         // This code works perfectly fine. | ||||
|         await oldContextAddInitScript.apply(this, [a, b]) | ||||
|         await tronApp.page.reload() | ||||
|       } | ||||
|  | ||||
|       // No idea why we mix and match page and context's addInitScript but we do | ||||
|       const oldPageAddInitScript = tronApp.page.addInitScript | ||||
|       tronApp.page.addInitScript = async function (a: any, b: any) { | ||||
|         // @ts-ignore pretty sure way out of tsc's type checking capabilities. | ||||
|         // This code works perfectly fine. | ||||
|         await oldPageAddInitScript.apply(this, [a, b]) | ||||
|         await tronApp.page.reload() | ||||
|       } | ||||
|  | ||||
|       // Create a consistent way to resize the page across electron and web. | ||||
|       // (lee) I had to do everyhting in the book to make electron change its | ||||
|       // damn window size. I succeded in making it consistently and reliably | ||||
|       // do it after a whole afternoon. | ||||
|       tronApp.page.setBodyDimensions = async function (dims: { | ||||
|         width: number | ||||
|         height: number | ||||
|       }) { | ||||
|         await tronApp.page.setViewportSize(dims) | ||||
|  | ||||
|         if (!(tronApp instanceof AuthenticatedTronApp)) { | ||||
|           return | ||||
|         } | ||||
|  | ||||
|         await tronApp.electronApp?.evaluateHandle(async ({ app }, dims) => { | ||||
|           // @ts-ignore sorry jon but see comment in main.ts why this is ignored | ||||
|           await app.resizeWindow(dims.width, dims.height) | ||||
|         }, dims) | ||||
|  | ||||
|         return tronApp.page.evaluate( | ||||
|           async (dims: { width: number; height: number }) => { | ||||
|             await window.electron.resizeWindow(dims.width, dims.height) | ||||
|             window.document.body.style.width = dims.width + 'px' | ||||
|             window.document.body.style.height = dims.height + 'px' | ||||
|             window.document.documentElement.style.width = dims.width + 'px' | ||||
|             window.document.documentElement.style.height = dims.height + 'px' | ||||
|           }, | ||||
|           dims | ||||
|         ) | ||||
|       } | ||||
|  | ||||
|       // We need to expose this in order for some tests that require folder | ||||
|       // creation. Before they used to do this by their own electronSetup({...}) | ||||
|       // calls. | ||||
|       if (tronApp instanceof AuthenticatedTronApp) { | ||||
|         tronApp.context.folderSetupFn = function (fn) { | ||||
|           return fn(tronApp.dir).then(() => ({ | ||||
|             dir: tronApp.dir, | ||||
|           })) | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       // tsc aint smart enough to know this'll never be undefined | ||||
|       // but I dont blame it, the logic to know is complex | ||||
|       if (fn) { | ||||
|         await fn( | ||||
|           { | ||||
|             context: tronApp.context, | ||||
|             page: tronApp.page, | ||||
|             electronApp: | ||||
|               tronApp instanceof AuthenticatedTronApp | ||||
|                 ? tronApp.electronApp | ||||
|                 : undefined, | ||||
|             ...fixtures, | ||||
|             request, | ||||
|             playwright, | ||||
|             browser, | ||||
|             acceptDownloads, | ||||
|             bypassCSP, | ||||
|             colorScheme, | ||||
|             clientCertificates, | ||||
|             deviceScaleFactor, | ||||
|             extraHTTPHeaders, | ||||
|             geolocation, | ||||
|             hasTouch, | ||||
|             httpCredentials, | ||||
|             ignoreHTTPSErrors, | ||||
|             isMobile, | ||||
|             javaScriptEnabled, | ||||
|             locale, | ||||
|             offline, | ||||
|             permissions, | ||||
|             proxy, | ||||
|             storageState, | ||||
|             timezoneId, | ||||
|             userAgent, | ||||
|             viewport, | ||||
|             baseURL, | ||||
|             contextOptions, | ||||
|             actionTimeout, | ||||
|             navigationTimeout, | ||||
|             serviceWorkers, | ||||
|             testIdAttribute, | ||||
|             browserName, | ||||
|             defaultBrowserType, | ||||
|             headless, | ||||
|             channel, | ||||
|             launchOptions, | ||||
|             connectOptions, | ||||
|             screenshot, | ||||
|             trace, | ||||
|             video, | ||||
|           }, | ||||
|           testInfo | ||||
|         ) | ||||
|       } | ||||
|  | ||||
|       testInfo.tronApp = | ||||
|         tronApp instanceof AuthenticatedTronApp ? tronApp : undefined | ||||
|     } | ||||
|   ) | ||||
| } | ||||
|  | ||||
| type ZooTest = typeof test | ||||
|  | ||||
| test.describe = pwTestFnWithFixtures.describe | ||||
| test.beforeEach = pwTestFnWithFixtures.beforeEach | ||||
| test.afterEach = pwTestFnWithFixtures.afterEach | ||||
| test.step = pwTestFnWithFixtures.step | ||||
| test.skip = pwTestFnWithFixtures.skip | ||||
| test.setTimeout = pwTestFnWithFixtures.setTimeout | ||||
| test.fixme = pwTestFnWithFixtures.fixme as unknown as ZooTest | ||||
| test.only = pwTestFnWithFixtures.only | ||||
| test.fail = pwTestFnWithFixtures.fail | ||||
| test.slow = pwTestFnWithFixtures.slow | ||||
| test.beforeAll = pwTestFnWithFixtures.beforeAll | ||||
| test.afterAll = pwTestFnWithFixtures.afterAll | ||||
| test.use = pwTestFnWithFixtures.use | ||||
| test.expect = pwTestFnWithFixtures.expect | ||||
| test.extend = pwTestFnWithFixtures.extend | ||||
| test.info = pwTestFnWithFixtures.info | ||||
							
								
								
									
										1
									
								
								interface.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -7,6 +7,7 @@ import { MachinesListing } from 'components/MachineManagerProvider' | ||||
| type EnvFn = (value?: string) => string | ||||
|  | ||||
| export interface IElectronAPI { | ||||
|   resizeWindow: (width: number, height: number) => Promise<void> | ||||
|   open: typeof dialog.showOpenDialog | ||||
|   save: typeof dialog.showSaveDialog | ||||
|   openExternal: typeof shell.openExternal | ||||
|  | ||||
							
								
								
									
										24
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @ -103,24 +103,22 @@ | ||||
|     "tron:package": "electron-forge package", | ||||
|     "tron:make": "electron-forge make", | ||||
|     "tron:publish": "electron-forge publish", | ||||
|     "tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron", | ||||
|     "chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'", | ||||
|     "tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", | ||||
|     "tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", | ||||
|     "tronb:package": "electron-builder --config electron-builder.yml", | ||||
|     "test-setup": "yarn install && yarn build:wasm", | ||||
|     "test": "vitest --mode development", | ||||
|     "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts", | ||||
|     "test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts", | ||||
|     "test:playwright:browser:chrome": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron'", | ||||
|     "test:playwright:browser:chrome:windows": "playwright test --project=\"Google Chrome\" --config=playwright.ci.config.ts --grep-invert=\"@snapshot|@electron|@skipWin\"", | ||||
|     "test:playwright:browser:chrome:ubuntu": "playwright test --project='Google Chrome' --config=playwright.ci.config.ts --grep-invert='@snapshot|@electron|@skipLinux'", | ||||
|     "test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep=@electron", | ||||
|     "test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin", | ||||
|     "test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos", | ||||
|     "test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux", | ||||
|     "test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron", | ||||
|     "test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin", | ||||
|     "test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos", | ||||
|     "test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux", | ||||
|     "test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", | ||||
|     "test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", | ||||
|     "test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", | ||||
|     "test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", | ||||
|     "test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", | ||||
|     "test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipWin|@snapshot'", | ||||
|     "test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", | ||||
|     "test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", | ||||
|     "test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000", | ||||
|     "test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000" | ||||
|   }, | ||||
| @ -158,8 +156,8 @@ | ||||
|     "@electron/rebuild": "^3.6.0", | ||||
|     "@iarna/toml": "^2.2.5", | ||||
|     "@lezer/generator": "^1.7.1", | ||||
|     "@playwright/test": "^1.49.0", | ||||
|     "@nabla/vite-plugin-eslint": "^2.0.5", | ||||
|     "@playwright/test": "^1.46.1", | ||||
|     "@testing-library/jest-dom": "^5.14.1", | ||||
|     "@testing-library/react": "^15.0.2", | ||||
|     "@types/d3-force": "^3.0.10", | ||||
|  | ||||
| @ -1,58 +0,0 @@ | ||||
| import { defineConfig, devices } from '@playwright/test' | ||||
|  | ||||
| /** | ||||
|  * See https://playwright.dev/docs/test-configuration. | ||||
|  */ | ||||
| export default defineConfig({ | ||||
|   timeout: 120_000, // override the default 30s timeout | ||||
|   testDir: './e2e/playwright', | ||||
|   /* Run tests in files in parallel */ | ||||
|   fullyParallel: true, | ||||
|   /* Fail the build on CI if you accidentally left test.only in the source code. */ | ||||
|   forbidOnly: true, | ||||
|   /* Do not retry */ | ||||
|   retries: 0, | ||||
|   /* Different amount of parallelism on CI and local. */ | ||||
|   workers: 1, | ||||
|   /* Reporter to use. See https://playwright.dev/docs/test-reporters */ | ||||
|   reporter: [ | ||||
|     ['dot'], | ||||
|     ['list'], | ||||
|     ['json', { outputFile: './test-results/report.json' }], | ||||
|     ['html'], | ||||
|   ], | ||||
|   /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ | ||||
|   use: { | ||||
|     /* Base URL to use in actions like `await page.goto('/')`. */ | ||||
|     baseURL: 'http://localhost:3000', | ||||
|  | ||||
|     /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ | ||||
|     trace: 'retain-on-failure', | ||||
|     actionTimeout: 15_000, | ||||
|     screenshot: 'only-on-failure', | ||||
|   }, | ||||
|  | ||||
|   /* Configure projects for major browsers */ | ||||
|   projects: [ | ||||
|     { | ||||
|       name: 'Google Chrome', | ||||
|       use: { | ||||
|         ...devices['Desktop Chrome'], | ||||
|         channel: 'chrome', | ||||
|         contextOptions: { | ||||
|           /* Chromium is the only one with these permission types */ | ||||
|           permissions: ['clipboard-write', 'clipboard-read'], | ||||
|         }, | ||||
|       }, // or 'chrome-beta' | ||||
|     }, | ||||
|     { | ||||
|       name: 'webkit', | ||||
|       use: { ...devices['Desktop Safari'] }, | ||||
|     }, | ||||
|   ], | ||||
|  | ||||
|   webServer: { | ||||
|     command: 'yarn start', | ||||
|     reuseExistingServer: false, | ||||
|   }, | ||||
| }) | ||||
| @ -45,7 +45,7 @@ export default class CodeManager { | ||||
|     } else if (storedCode === null) { | ||||
|       this.code = bracket | ||||
|     } else { | ||||
|       this.code = storedCode | ||||
|       this.code = storedCode || '' | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -57,6 +57,10 @@ export default class CodeManager { | ||||
|     return this._code | ||||
|   } | ||||
|  | ||||
|   localStoragePersistCode(): string { | ||||
|     return safeLSGetItem(PERSIST_CODE_KEY) || '' | ||||
|   } | ||||
|  | ||||
|   registerCallBacks({ setCode }: { setCode: (arg: string) => void }) { | ||||
|     this.#updateState = setCode | ||||
|   } | ||||
| @ -165,7 +169,7 @@ export default class CodeManager { | ||||
| } | ||||
|  | ||||
| function safeLSGetItem(key: string) { | ||||
|   if (typeof window === 'undefined') return null | ||||
|   if (typeof window === 'undefined') return | ||||
|   return localStorage?.getItem(key) | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -17,9 +17,14 @@ const save_ = async (file: ModelingAppFile, toastId: string) => { | ||||
|       } | ||||
|  | ||||
|       if (window.electron.process.env.IS_PLAYWRIGHT) { | ||||
|         // skip file picker, save to default location | ||||
|         // Skip file picker, save to the test dir downloads directory | ||||
|         const downloadDir = window.electron.join( | ||||
|           window.electron.process.env.TEST_SETTINGS_FILE_KEY, | ||||
|           'downloads-during-playwright' | ||||
|         ) | ||||
|         await window.electron.mkdir(downloadDir, { recursive: true }) | ||||
|         await window.electron.writeFile( | ||||
|           file.name, | ||||
|           window.electron.join(downloadDir, file.name), | ||||
|           new Uint8Array(file.contents) | ||||
|         ) | ||||
|         toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId }) | ||||
|  | ||||
| @ -85,12 +85,13 @@ export const fileLoader: LoaderFunction = async ( | ||||
|   ) | ||||
|   const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH) | ||||
|  | ||||
|   let code = '' | ||||
|  | ||||
|   if (!isBrowserProject && projectPathData) { | ||||
|     const { projectName, projectPath, currentFileName, currentFilePath } = | ||||
|       projectPathData | ||||
|  | ||||
|     const urlObj = new URL(routerData.request.url) | ||||
|     let code = '' | ||||
|  | ||||
|     if (!urlObj.pathname.endsWith('/settings')) { | ||||
|       const fallbackFile = isDesktop() | ||||
| @ -120,6 +121,10 @@ export const fileLoader: LoaderFunction = async ( | ||||
|       }) | ||||
|       code = normalizeLineEndings(code) | ||||
|  | ||||
|       // If persistCode in localStorage is present, it'll persist that code | ||||
|       // through *anything*. INTENDED FOR TESTS. | ||||
|       code = codeManager.localStoragePersistCode() || code | ||||
|  | ||||
|       // Update both the state and the editor's code. | ||||
|       // We explicitly do not write to the file here since we are loading from | ||||
|       // the file system and not the editor. | ||||
| @ -147,12 +152,6 @@ export const fileLoader: LoaderFunction = async ( | ||||
|       ? await getProjectInfo(projectPath) | ||||
|       : null | ||||
|  | ||||
|     console.log('maybeProjectInfo', { | ||||
|       maybeProjectInfo, | ||||
|       defaultProjectData, | ||||
|       projectPathData, | ||||
|     }) | ||||
|  | ||||
|     const projectData: IndexLoaderData = { | ||||
|       code, | ||||
|       project: maybeProjectInfo ?? defaultProjectData, | ||||
| @ -169,7 +168,7 @@ export const fileLoader: LoaderFunction = async ( | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     code: '', | ||||
|     code, | ||||
|     project: { | ||||
|       name: BROWSER_PROJECT_NAME, | ||||
|       path: '/' + BROWSER_PROJECT_NAME, | ||||
|  | ||||
							
								
								
									
										11
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						| @ -139,6 +139,17 @@ app.on('ready', (event, data) => { | ||||
| // There is just not enough code to warrant it and further abstracts everything | ||||
| // which is already quite abstracted | ||||
|  | ||||
| // @ts-ignore | ||||
| // electron/electron.d.ts has done type = App, making declaration merging not | ||||
| // possible :( | ||||
| app.resizeWindow = async (width: number, height: number) => { | ||||
|   return mainWindow?.setSize(width, height) | ||||
| } | ||||
|  | ||||
| ipcMain.handle('app.resizeWindow', (event, data) => { | ||||
|   return mainWindow?.setSize(data[0], data[1]) | ||||
| }) | ||||
|  | ||||
| ipcMain.handle('app.getPath', (event, data) => { | ||||
|   return app.getPath(data) | ||||
| }) | ||||
|  | ||||
| @ -7,6 +7,8 @@ import packageJson from '../package.json' | ||||
| import { MachinesListing } from 'components/MachineManagerProvider' | ||||
| import chokidar from 'chokidar' | ||||
|  | ||||
| const resizeWindow = (width: number, height: number) => | ||||
|   ipcRenderer.invoke('app.resizeWindow', [width, height]) | ||||
| const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args) | ||||
| const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args) | ||||
| const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url) | ||||
| @ -18,12 +20,16 @@ const loginWithDeviceFlow = (): Promise<string> => | ||||
|   ipcRenderer.invoke('loginWithDeviceFlow') | ||||
| const onUpdateDownloaded = ( | ||||
|   callback: (value: { version: string; releaseNotes: string }) => void | ||||
| ) => ipcRenderer.on('update-downloaded', (_event, value) => callback(value)) | ||||
| ) => | ||||
|   ipcRenderer.on('update-downloaded', (_event: any, value) => callback(value)) | ||||
| const onUpdateDownloadStart = ( | ||||
|   callback: (value: { version: string }) => void | ||||
| ) => ipcRenderer.on('update-download-start', (_event, value) => callback(value)) | ||||
| ) => | ||||
|   ipcRenderer.on('update-download-start', (_event: any, value) => | ||||
|     callback(value) | ||||
|   ) | ||||
| const onUpdateError = (callback: (value: Error) => void) => | ||||
|   ipcRenderer.on('update-error', (_event, value) => callback(value)) | ||||
|   ipcRenderer.on('update-error', (_event: any, value) => callback(value)) | ||||
| const appRestart = () => ipcRenderer.invoke('app.restart') | ||||
|  | ||||
| const isMac = os.platform() === 'darwin' | ||||
| @ -189,4 +195,5 @@ contextBridge.exposeInMainWorld('electron', { | ||||
|   onUpdateError, | ||||
|   appRestart, | ||||
|   getArgvParsed, | ||||
|   resizeWindow, | ||||
| }) | ||||
|  | ||||
							
								
								
									
										28
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						| @ -2258,12 +2258,12 @@ | ||||
|   resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" | ||||
|   integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== | ||||
|  | ||||
| "@playwright/test@^1.46.1": | ||||
|   version "1.46.1" | ||||
|   resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.46.1.tgz#a8dfdcd623c4c23bb1b7ea588058aad41055c188" | ||||
|   integrity sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA== | ||||
| "@playwright/test@^1.49.0": | ||||
|   version "1.49.0" | ||||
|   resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.0.tgz#74227385b58317ee076b86b56d0e1e1b25cff01e" | ||||
|   integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw== | ||||
|   dependencies: | ||||
|     playwright "1.46.1" | ||||
|     playwright "1.49.0" | ||||
|  | ||||
| "@react-hook/latest@^1.0.2": | ||||
|   version "1.0.3" | ||||
| @ -7800,17 +7800,17 @@ pkg-types@^1.0.3, pkg-types@^1.1.1: | ||||
|     mlly "^1.7.1" | ||||
|     pathe "^1.1.2" | ||||
|  | ||||
| playwright-core@1.46.1: | ||||
|   version "1.46.1" | ||||
|   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.46.1.tgz#28f3ab35312135dda75b0c92a3e5c0e7edb9cc8b" | ||||
|   integrity sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A== | ||||
| playwright-core@1.49.0: | ||||
|   version "1.49.0" | ||||
|   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.49.0.tgz#8e69ffed3f41855b854982f3632f2922c890afcb" | ||||
|   integrity sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA== | ||||
|  | ||||
| playwright@1.46.1: | ||||
|   version "1.46.1" | ||||
|   resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.46.1.tgz#ea562bc48373648e10420a10c16842f0b227c218" | ||||
|   integrity sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng== | ||||
| playwright@1.49.0: | ||||
|   version "1.49.0" | ||||
|   resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.49.0.tgz#df6b9e05423377a99658202844a294a8afb95d0a" | ||||
|   integrity sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A== | ||||
|   dependencies: | ||||
|     playwright-core "1.46.1" | ||||
|     playwright-core "1.49.0" | ||||
|   optionalDependencies: | ||||
|     fsevents "2.3.2" | ||||
|  | ||||
|  | ||||
