Compare commits
	
		
			74 Commits
		
	
	
		
			franknoiro
			...
			achalmers/
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bb97fb7257 | |||
| bff0bd8696 | |||
| 223e0c0edd | |||
| 5d4f3ea649 | |||
| 048bc4c561 | |||
| e8fc6bc037 | |||
| 5bdd090119 | |||
| 669cab8737 | |||
| f1ea60d6ab | |||
| 3faec650b1 | |||
| b2b62ec163 | |||
| 5b798c2aa3 | |||
| a23bd1f034 | |||
| 4d00dddfd8 | |||
| f055acb6a6 | |||
| bf9d88e9a5 | |||
| 712a3790e8 | |||
| f1ab8602a2 | |||
| a1f72b1d5a | |||
| f9048b8882 | |||
| 4b0f83d3ac | |||
| 62c27e0809 | |||
| 6a09bbc0ef | |||
| 997f60e1e5 | |||
| 6f1d7138c0 | |||
| 3dabab2c74 | |||
| 13986fcfd7 | |||
| d1e21d673e | |||
| ca3a88b4df | |||
| fb57df2cad | |||
| 57a91cdb26 | |||
| 33079b4151 | |||
| a75157573b | |||
| e29345fbf6 | |||
| 35c7183809 | |||
| b9fe3ed9e0 | |||
| 5a25b60485 | |||
| 4b9f71c994 | |||
| e86a5622c8 | |||
| a503d1ce50 | |||
| 11a94cc99e | |||
| e295f82495 | |||
| 0b9cf2dc21 | |||
| eb58507e93 | |||
| ca28a5f549 | |||
| 6f4bbdb79e | |||
| 6773dbe7ff | |||
| acfc2b47fa | |||
| 9dc7ff9797 | |||
| 308a0fb06e | |||
| 214ae6f512 | |||
| 8d54fec589 | |||
| 4bfbecd3e7 | |||
| dff3848a00 | |||
| f875efab1b | |||
| 3f082c8222 | |||
| e1c45bdb33 | |||
| 5cb5dbd689 | |||
| 0a8881bc69 | |||
| be9438160e | |||
| 77b565f781 | |||
| c843dfad95 | |||
| a3ff0a45eb | |||
| 4617ad0fed | |||
| 5fa51a2f92 | |||
| 4218777afb | |||
| 8b1b462e29 | |||
| 2bc99ba39b | |||
| ffe0da6dcd | |||
| d27afb8c74 | |||
| 1c778bf373 | |||
| 5df8a943a9 | |||
| ab729dbcdb | |||
| 84865eaed0 | 
| @ -1,3 +1,3 @@ | ||||
| [codespell] | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall,ser | ||||
| skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas | ||||
|  | ||||
							
								
								
									
										12
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -89,16 +89,20 @@ jobs: | ||||
|       - run: yarn build:wasm | ||||
|  | ||||
|       - run: yarn simpleserver:ci | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|  | ||||
|       - name: Install Chromium Browser | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         run: yarn playwright install chromium --with-deps | ||||
|  | ||||
|       - name: run unit tests | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         run: yarn test:nowatch | ||||
|         env: | ||||
|           VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||
|  | ||||
|       - name: check for changes | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         id: git-check | ||||
|         run: | | ||||
|             git add src/lang/std/artifactMapGraphs | ||||
| @ -107,7 +111,7 @@ jobs: | ||||
|             else echo "modified=false" >> $GITHUB_OUTPUT | ||||
|             fi | ||||
|       - name: Commit changes, if any | ||||
|         if: steps.git-check.outputs.modified == 'true' | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' && steps.git-check.outputs.modified == 'true' }} | ||||
|         run: | | ||||
|           git config --local user.email "github-actions[bot]@users.noreply.github.com" | ||||
|           git config --local user.name "github-actions[bot]" | ||||
| @ -531,7 +535,7 @@ jobs: | ||||
|           project_id: kittycadapi | ||||
|  | ||||
|       - name: Upload release files to public bucket | ||||
|         uses: google-github-actions/upload-cloud-storage@v2.1.0 | ||||
|         uses: google-github-actions/upload-cloud-storage@v2.1.1 | ||||
|         with: | ||||
|           path: artifact | ||||
|           glob: '*/Zoo*' | ||||
| @ -539,13 +543,13 @@ jobs: | ||||
|           destination: ${{ env.BUCKET_DIR }}/${{ env.VERSION }} | ||||
|  | ||||
|       - name: Upload update endpoint to public bucket | ||||
|         uses: google-github-actions/upload-cloud-storage@v2.1.0 | ||||
|         uses: google-github-actions/upload-cloud-storage@v2.1.1 | ||||
|         with: | ||||
|           path: last_update.json | ||||
|           destination: ${{ env.BUCKET_DIR }} | ||||
|  | ||||
|       - name: Upload download endpoint to public bucket | ||||
|         uses: google-github-actions/upload-cloud-storage@v2.1.0 | ||||
|         uses: google-github-actions/upload-cloud-storage@v2.1.1 | ||||
|         with: | ||||
|           path: last_download.json | ||||
|           destination: ${{ env.BUCKET_DIR }} | ||||
|  | ||||
							
								
								
									
										12
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -35,7 +35,7 @@ jobs: | ||||
|  | ||||
|   playwright-ubuntu: | ||||
|     timeout-minutes: 30 | ||||
|     runs-on: ubuntu-latest-8-cores | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
| @ -112,7 +112,7 @@ jobs: | ||||
|       run: yarn build:local | ||||
|     - name: Run ubuntu/chrome snapshots | ||||
|       run: | | ||||
|         yarn playwright test --project="Google Chrome" --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | ||||
|         yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --retries="3" --update-snapshots --grep=@snapshot  --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | ||||
|       env: | ||||
|         CI: true | ||||
|         token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||
| @ -171,7 +171,7 @@ jobs: | ||||
|         if [[ ! -f "test-results/.last-run.json" ]]; then | ||||
|             # if no last run artifact, than run plawright normally | ||||
|             echo "run playwright normally" | ||||
|             yarn playwright test --project="Google Chrome" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true | ||||
|             yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true | ||||
|             # # send to axiom | ||||
|             node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 | ||||
|         fi | ||||
| @ -186,7 +186,7 @@ jobs: | ||||
|                 if [[ $failed_tests -gt 0 ]]; then | ||||
|                     echo "retried=true" >>$GITHUB_OUTPUT | ||||
|                     echo "run playwright with last failed tests and retry $retry" | ||||
|                     yarn playwright test --project="Google Chrome" --last-failed --grep-invert=@snapshot || true | ||||
|                     yarn playwright test --project="Google Chrome" --config=playwright.ci.config.ts --last-failed --grep-invert=@snapshot || true | ||||
|                     # send to axiom | ||||
|                     node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 | ||||
|                     retry=$((retry + 1)) | ||||
| @ -325,7 +325,7 @@ jobs: | ||||
|         if [[ ! -f "test-results/.last-run.json" ]]; then | ||||
|             # if no last run artifact, than run plawright normally | ||||
|             echo "run playwright normally" | ||||
|             yarn playwright test --project="webkit" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true | ||||
|             yarn playwright test --project="webkit" --config=playwright.ci.config.ts --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true | ||||
|             # # send to axiom | ||||
|             node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 | ||||
|         fi | ||||
| @ -340,7 +340,7 @@ jobs: | ||||
|                 if [[ $failed_tests -gt 0 ]]; then | ||||
|                     echo "retried=true" >>$GITHUB_OUTPUT | ||||
|                     echo "run playwright with last failed tests and retry $retry" | ||||
|                     yarn playwright test --project="webkit" --last-failed --grep-invert=@snapshot || true | ||||
|                     yarn playwright test --project="webkit" --config=playwright.ci.config.ts --last-failed --grep-invert=@snapshot || true | ||||
|                     # send to axiom | ||||
|                     node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 | ||||
|                     retry=$((retry + 1)) | ||||
|  | ||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @ -110,6 +110,17 @@ Note that these became separate apps on Macos, so make sure you open the right o | ||||
|  | ||||
| <img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png"> | ||||
|  | ||||
| ## Checking out commits / Bisecting | ||||
|  | ||||
| Which commands from setup are one off vs need to be run every time? | ||||
|  | ||||
| The following will need to be run when checking out a new commit and guarantees the build is not stale: | ||||
| ```bash | ||||
| yarn install | ||||
| yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build | ||||
| yarn start # or yarn build:local && yarn serve for slower but more production-like build | ||||
| ``` | ||||
|  | ||||
| ## Before submitting a PR | ||||
|  | ||||
| Before you submit a contribution PR to this repo, please ensure that: | ||||
|  | ||||
| @ -25,5 +25,5 @@ once fixed in engine will just start working here with no language changes. | ||||
|  | ||||
|     Sketching on the chamfered face does not currently work. | ||||
|  | ||||
| - **Shell**: Shell is only working for `end` faces, not for `side` or `start`  | ||||
|     faces. We are tracking the engine side bug on this. | ||||
| - **Shell**: Shell sometimes does not work when arcs or fillets are involved. | ||||
|     We are tracking the engine side bug on this. | ||||
|  | ||||
							
								
								
									
										37
									
								
								docs/kcl/assertEqual.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -21,6 +21,7 @@ layout: manual | ||||
| * [`arc`](kcl/arc) | ||||
| * [`asin`](kcl/asin) | ||||
| * [`assert`](kcl/assert) | ||||
| * [`assertEqual`](kcl/assertEqual) | ||||
| * [`assertGreaterThan`](kcl/assertGreaterThan) | ||||
| * [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq) | ||||
| * [`assertLessThan`](kcl/assertLessThan) | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| --- | ||||
| title: "legAngX" | ||||
| excerpt: "Returns the angle of the given leg for x." | ||||
| excerpt: "Compute the angle of the given leg for x." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Returns the angle of the given leg for x. | ||||
| Compute the angle of the given leg for x. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| --- | ||||
| title: "legAngY" | ||||
| excerpt: "Returns the angle of the given leg for y." | ||||
| excerpt: "Compute the angle of the given leg for y." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Returns the angle of the given leg for y. | ||||
| Compute the angle of the given leg for y. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,10 +1,10 @@ | ||||
| --- | ||||
| title: "legLen" | ||||
| excerpt: "Returns the length of the given leg." | ||||
| excerpt: "Compute the length of the given leg." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Returns the length of the given leg. | ||||
| Compute the length of the given leg. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										3734
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										153
									
								
								e2e/playwright/basic-sketch.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,153 @@ | ||||
| import { test, expect, Page } from '@playwright/test' | ||||
| import { | ||||
|   getUtils, | ||||
|   TEST_COLORS, | ||||
|   setup, | ||||
|   tearDown, | ||||
|   commonPoints, | ||||
|   PERSIST_MODELING_CONTEXT, | ||||
| } from './test-utils' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.setTimeout(120000) | ||||
|  | ||||
| async function doBasicSketch(page: Page, openPanes: string[]) { | ||||
|   const u = await getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|  | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await u.openDebugPanel() | ||||
|  | ||||
|   // If we have the code pane open, we should see the code. | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(u.codeLocator).toHaveText(``) | ||||
|   } else { | ||||
|     // Ensure we don't see the code. | ||||
|     await expect(u.codeLocator).not.toBeVisible() | ||||
|   } | ||||
|  | ||||
|   await expect( | ||||
|     page.getByRole('button', { name: 'Start Sketch' }) | ||||
|   ).not.toBeDisabled() | ||||
|   await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() | ||||
|  | ||||
|   // click on "Start Sketch" button | ||||
|   await u.clearCommandLogs() | ||||
|   await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   // select a plane | ||||
|   await page.mouse.click(700, 200) | ||||
|  | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(u.codeLocator).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')` | ||||
|     ) | ||||
|   } | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   await page.waitForTimeout(1000) // TODO detect animation ending, or disable animation | ||||
|  | ||||
|   const startXPx = 600 | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(u.codeLocator) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt(${commonPoints.startAt}, %)`) | ||||
|   } | ||||
|   await page.waitForTimeout(500) | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|   await page.waitForTimeout(500) | ||||
|  | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(u.codeLocator) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt(${commonPoints.startAt}, %) | ||||
|   |> line([${commonPoints.num1}, 0], %)`) | ||||
|   } | ||||
|   await page.waitForTimeout(500) | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(u.codeLocator) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt(${commonPoints.startAt}, %) | ||||
|   |> line([${commonPoints.num1}, 0], %) | ||||
|   |> line([0, ${commonPoints.num1 + 0.01}], %)`) | ||||
|   } else { | ||||
|     await page.waitForTimeout(500) | ||||
|   } | ||||
|   await page.mouse.click(startXPx, 500 - PUR * 20) | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(u.codeLocator) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt(${commonPoints.startAt}, %) | ||||
|   |> line([${commonPoints.num1}, 0], %) | ||||
|   |> line([0, ${commonPoints.num1 + 0.01}], %) | ||||
|   |> line([-${commonPoints.num2}, 0], %)`) | ||||
|   } | ||||
|  | ||||
|   // deselect line tool | ||||
|   await page.getByRole('button', { name: 'Line', exact: true }).click() | ||||
|   await page.waitForTimeout(500) | ||||
|  | ||||
|   const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0) | ||||
|   if (openPanes.includes('code')) { | ||||
|     expect(await u.getGreatestPixDiff(line1, TEST_COLORS.WHITE)).toBeLessThan(3) | ||||
|     await expect( | ||||
|       await u.getGreatestPixDiff(line1, [249, 249, 249]) | ||||
|     ).toBeLessThan(3) | ||||
|   } | ||||
|   // click between first two clicks to get center of the line | ||||
|   await page.mouse.click(startXPx + PUR * 15, 500 - PUR * 10) | ||||
|   await page.waitForTimeout(100) | ||||
|   if (openPanes.includes('code')) { | ||||
|     expect(await u.getGreatestPixDiff(line1, TEST_COLORS.BLUE)).toBeLessThan(3) | ||||
|     await expect(await u.getGreatestPixDiff(line1, [0, 0, 255])).toBeLessThan(3) | ||||
|   } | ||||
|  | ||||
|   // hold down shift | ||||
|   await page.keyboard.down('Shift') | ||||
|   // click between the latest two clicks to get center of the line | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 20) | ||||
|  | ||||
|   // selected two lines therefore there should be two cursors | ||||
|   if (openPanes.includes('code')) { | ||||
|     await expect(page.locator('.cm-cursor')).toHaveCount(2) | ||||
|   } | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Length: open menu' }).click() | ||||
|   await page.getByRole('button', { name: 'Equal Length' }).click() | ||||
|  | ||||
|   // Open the code pane. | ||||
|   await u.openKclCodePanel() | ||||
|   await expect(u.codeLocator).toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt(${commonPoints.startAt}, %) | ||||
|   |> line([${commonPoints.num1}, 0], %, $seg01) | ||||
|   |> line([0, ${commonPoints.num1 + 0.01}], %) | ||||
|   |> angledLine([180, segLen(seg01)], %)`) | ||||
| } | ||||
|  | ||||
| test.describe('Basic sketch', () => { | ||||
|   test('code pane open at start', async ({ page }) => { | ||||
|     await doBasicSketch(page, ['code']) | ||||
|   }) | ||||
|  | ||||
|   test('code pane closed at start', async ({ page }) => { | ||||
|     // Load the app with the code panes | ||||
|     await page.addInitScript(async (persistModelingContext) => { | ||||
|       localStorage.setItem( | ||||
|         persistModelingContext, | ||||
|         JSON.stringify({ openPanes: [] }) | ||||
|       ) | ||||
|     }, PERSIST_MODELING_CONTEXT) | ||||
|     await doBasicSketch(page, []) | ||||
|   }) | ||||
| }) | ||||
| @ -0,0 +1,111 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { EngineCommand } from 'lang/std/artifactGraph' | ||||
| import { uuidv4 } from 'lib/utils' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| 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, | ||||
|     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 u.waitForAuthSkipAppStart() | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     const coord = | ||||
|       plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100 | ||||
|     const camCommand: EngineCommand = { | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_look_at', | ||||
|         center: { x: 0, y: 0, z: 0 }, | ||||
|         vantage: { x: coord, y: coord, z: coord }, | ||||
|         up: { x: 0, y: 0, z: 1 }, | ||||
|       }, | ||||
|     } | ||||
|     const updateCamCommand: EngineCommand = { | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_get_settings', | ||||
|       }, | ||||
|     } | ||||
|  | ||||
|     const code = `const sketch001 = startSketchOn('${plane}') | ||||
|     |> startProfileAt([0.9, -1.22], %)` | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|  | ||||
|     await u.clearCommandLogs() | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|  | ||||
|     await u.sendCustomCmd(camCommand) | ||||
|     await page.waitForTimeout(100) | ||||
|     await u.sendCustomCmd(updateCamCommand) | ||||
|  | ||||
|     await u.closeDebugPanel() | ||||
|     await page.mouse.click(clickCoords.x, clickCoords.y) | ||||
|     await page.waitForTimeout(300) // wait for animation | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Line', exact: true }) | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     // draw a line | ||||
|     const startXPx = 600 | ||||
|  | ||||
|     await u.closeDebugPanel() | ||||
|     await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(code) | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Line', exact: true }).click() | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await page.getByRole('button', { name: 'Exit Sketch' }).click() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|  | ||||
|     await u.clearCommandLogs() | ||||
|     await u.removeCurrentCode() | ||||
|   } | ||||
|   test('XY', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest( | ||||
|       page, | ||||
|       '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('XZ', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane | ||||
|   }) | ||||
|  | ||||
|   test('-XY', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, '-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('-XZ', async ({ page }) => { | ||||
|     await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										219
									
								
								e2e/playwright/code-pane-and-errors.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,219 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
|  | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { bracket } from 'lib/exampleKcl' | ||||
| import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| 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, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
|     await page.addInitScript((code) => { | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, bracket) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // Ensure no badge is present | ||||
|     const codePaneButtonHolder = page.locator('#code-button-holder') | ||||
|     await expect(codePaneButtonHolder).not.toContainText('notification') | ||||
|  | ||||
|     // Delete a character to break the KCL | ||||
|     await u.openKclCodePanel() | ||||
|     await page.getByText('extrude(').click() | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     // Ensure that a badge appears on the button | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
|   }) | ||||
|  | ||||
|   test('Opening and closing the code pane will consistently show error diagnostics', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
|     await page.addInitScript((code) => { | ||||
|       localStorage.setItem('persistCode', code) | ||||
|     }, bracket) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 900 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // Ensure we have no errors in the gutter. | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // Ensure no badge is present | ||||
|     const codePaneButton = page.getByRole('button', { name: 'KCL Code pane' }) | ||||
|     const codePaneButtonHolder = page.locator('#code-button-holder') | ||||
|     await expect(codePaneButtonHolder).not.toContainText('notification') | ||||
|  | ||||
|     // Delete a character to break the KCL | ||||
|     await u.openKclCodePanel() | ||||
|     await page.getByText('extrude(').click() | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     // Ensure that a badge appears on the button | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
|  | ||||
|     // Ensure we have an error diagnostic. | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|  | ||||
|     // error text on hover | ||||
|     await page.hover('.cm-lint-marker-error') | ||||
|     await expect(page.getByText('Unexpected token').first()).toBeVisible() | ||||
|  | ||||
|     // Close the code pane | ||||
|     await codePaneButton.click() | ||||
|  | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     // Ensure that a badge appears on the button | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
|     // Ensure we have no errors in the gutter. | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // Open the code pane | ||||
|     await u.openKclCodePanel() | ||||
|  | ||||
|     // Ensure that a badge appears on the button | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
|  | ||||
|     // Ensure we have an error diagnostic. | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|  | ||||
|     // error text on hover | ||||
|     await page.hover('.cm-lint-marker-error') | ||||
|     await expect(page.getByText('Unexpected token').first()).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('When error is not in view you can click the badge to scroll to it', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
|     await page.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.waitForTimeout(1000) | ||||
|  | ||||
|     // Ensure badge is present | ||||
|     const codePaneButtonHolder = page.locator('#code-button-holder') | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
|  | ||||
|     // Ensure we have no errors in the gutter, since error out of view. | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // Click the badge. | ||||
|     const badge = page.locator('#code-badge') | ||||
|     await expect(badge).toBeVisible() | ||||
|     await badge.click() | ||||
|  | ||||
|     // Ensure we have an error diagnostic. | ||||
|     await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() | ||||
|  | ||||
|     // Hover over the error to see the error message | ||||
|     await page.hover('.cm-lint-marker-error') | ||||
|     await expect( | ||||
|       page | ||||
|         .getByText( | ||||
|           '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 ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     // Load the app with the working starter code | ||||
|     await page.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.waitForTimeout(1000) | ||||
|  | ||||
|     // Ensure badge is present | ||||
|     const codePaneButtonHolder = page.locator('#code-button-holder') | ||||
|     await expect(codePaneButtonHolder).toContainText('notification') | ||||
|  | ||||
|     // Ensure we have no errors in the gutter, since error out of view. | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // click in the editor to focus it | ||||
|     await page.locator('.cm-content').click() | ||||
|  | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     // go to the start of the editor and enter more text which will trigger | ||||
|     // a lint error. | ||||
|     // GO to the start of the editor. | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('ArrowUp') | ||||
|     await page.keyboard.press('Home') | ||||
|     await page.keyboard.type('const foo_bar = 1') | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     // ensure we have a lint error | ||||
|     await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() | ||||
|  | ||||
|     // Click the badge. | ||||
|     const badge = page.locator('#code-badge') | ||||
|     await expect(badge).toBeVisible() | ||||
|     await badge.click() | ||||
|  | ||||
|     // Ensure we have an error diagnostic. | ||||
|     await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() | ||||
|  | ||||
|     // Hover over the error to see the error message | ||||
|     await page.hover('.cm-lint-marker-error') | ||||
|     await expect( | ||||
|       page | ||||
|         .getByText( | ||||
|           'sketch profile must lie entirely on one side of the revolution axis' | ||||
|         ) | ||||
|         .first() | ||||
|     ).toBeVisible() | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										364
									
								
								e2e/playwright/command-bar-tests.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,364 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
|  | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
| import { KCL_DEFAULT_LENGTH } from 'lib/constants' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| 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, | ||||
|   }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XY') | ||||
|     |> 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 u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // Click the line of code for xLine. | ||||
|     await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick() | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await page.getByRole('button', { name: 'Extrude' }).click() | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-activeLine')).toHaveText( | ||||
|       `const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)` | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Fillet from command bar', async ({ page }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-5, -5], %) | ||||
|   |> line([0, 10], %) | ||||
|   |> line([10, 0], %) | ||||
|   |> line([0, -10], %) | ||||
|   |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||
|   |> close(%) | ||||
| const extrude001 = extrude(-10, sketch001)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     const selectSegment = () => page.getByText(`line([0, -10], %)`).click() | ||||
|  | ||||
|     await selectSegment() | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.getByRole('button', { name: 'Fillet' }).click() | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-activeLine')).toContainText( | ||||
|       `fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)` | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Command bar can change a setting, and switch back and forth between arguments', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     const commandBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     const cmdSearchBar = page.getByPlaceholder('Search commands') | ||||
|     const commandName = 'debug panel' | ||||
|     const commandOption = page.getByRole('option', { | ||||
|       name: commandName, | ||||
|       exact: false, | ||||
|     }) | ||||
|     const commandLevelArgButton = page.getByRole('button', { name: 'level' }) | ||||
|     const commandThemeArgButton = page.getByRole('button', { name: 'value' }) | ||||
|     const paneSelector = page.getByRole('button', { name: 'debug panel' }) | ||||
|     // This selector changes after we set the setting | ||||
|     let commandOptionInput = page.getByPlaceholder('On') | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
|  | ||||
|     // First try opening the command bar and closing it | ||||
|     await page | ||||
|       .getByRole('button', { name: 'Commands', exact: false }) | ||||
|       .or(page.getByRole('button', { name: '⌘K' })) | ||||
|       .click() | ||||
|  | ||||
|     await expect(cmdSearchBar).toBeVisible() | ||||
|     await page.keyboard.press('Escape') | ||||
|     await expect(cmdSearchBar).not.toBeVisible() | ||||
|  | ||||
|     // Now try the same, but with the keyboard shortcut, check focus | ||||
|     await page.keyboard.press('Meta+K') | ||||
|     await expect(cmdSearchBar).toBeVisible() | ||||
|     await expect(cmdSearchBar).toBeFocused() | ||||
|  | ||||
|     // Try typing in the command bar | ||||
|     await cmdSearchBar.fill(commandName) | ||||
|     await expect(commandOption).toBeVisible() | ||||
|     await commandOption.click() | ||||
|     const toggleInput = page.getByPlaceholder('On') | ||||
|     await expect(toggleInput).toBeVisible() | ||||
|     await expect(toggleInput).toBeFocused() | ||||
|     // Select On | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await expect(page.getByRole('option', { name: 'Off' })).toHaveAttribute( | ||||
|       'data-headlessui-state', | ||||
|       'active' | ||||
|     ) | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     // Check the toast appeared | ||||
|     await expect( | ||||
|       page.getByText(`Set show debug panel to "false" for this project`) | ||||
|     ).toBeVisible() | ||||
|     // Check that the visibility changed | ||||
|     await expect(paneSelector).not.toBeVisible() | ||||
|  | ||||
|     commandOptionInput = page.getByPlaceholder('off') | ||||
|  | ||||
|     // Test case for https://github.com/KittyCAD/modeling-app/issues/2882 | ||||
|     await commandBarButton.click() | ||||
|     await cmdSearchBar.focus() | ||||
|     await cmdSearchBar.fill(commandName) | ||||
|     await commandOption.click() | ||||
|     await expect(commandThemeArgButton).toBeDisabled() | ||||
|     await commandOptionInput.focus() | ||||
|     await commandOptionInput.fill('on') | ||||
|     await commandLevelArgButton.click() | ||||
|     await expect(commandLevelArgButton).toBeDisabled() | ||||
|  | ||||
|     // Test case for https://github.com/KittyCAD/modeling-app/issues/2881 | ||||
|     await commandThemeArgButton.click() | ||||
|     await expect(commandThemeArgButton).toBeDisabled() | ||||
|     await expect(commandLevelArgButton).toHaveText('level: project') | ||||
|   }) | ||||
|  | ||||
|   test('Command bar keybinding works from code editor and can change a setting', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
|  | ||||
|     // Put the cursor in the code editor | ||||
|     await page.locator('.cm-content').click() | ||||
|  | ||||
|     // Now try the same, but with the keyboard shortcut, check focus | ||||
|     await page.keyboard.press('Meta+K') | ||||
|  | ||||
|     let cmdSearchBar = page.getByPlaceholder('Search commands') | ||||
|     await expect(cmdSearchBar).toBeVisible() | ||||
|     await expect(cmdSearchBar).toBeFocused() | ||||
|  | ||||
|     // Try typing in the command bar | ||||
|     await cmdSearchBar.fill('theme') | ||||
|     const themeOption = page.getByRole('option', { | ||||
|       name: 'Settings · app · theme', | ||||
|     }) | ||||
|     await expect(themeOption).toBeVisible() | ||||
|     await themeOption.click() | ||||
|     const themeInput = page.getByPlaceholder('dark') | ||||
|     await expect(themeInput).toBeVisible() | ||||
|     await expect(themeInput).toBeFocused() | ||||
|     // Select dark theme | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute( | ||||
|       'data-headlessui-state', | ||||
|       'active' | ||||
|     ) | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     // Check the toast appeared | ||||
|     await expect( | ||||
|       page.getByText(`Set theme to "system" as a user default`) | ||||
|     ).toBeVisible() | ||||
|     // Check that the theme changed | ||||
|     await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) | ||||
|   }) | ||||
|  | ||||
|   test('Can extrude from the command bar', async ({ page }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const distance = sqrt(20) | ||||
|       const 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 u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // Make sure the stream is up | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|  | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
|     await u.clearCommandLogs() | ||||
|     await page.getByRole('button', { name: 'Extrude' }).isEnabled() | ||||
|  | ||||
|     let cmdSearchBar = page.getByPlaceholder('Search commands') | ||||
|     await page.keyboard.press('Meta+K') | ||||
|     await expect(cmdSearchBar).toBeVisible() | ||||
|  | ||||
|     // Search for extrude command and choose it | ||||
|     await page.getByRole('option', { name: 'Extrude' }).click() | ||||
|  | ||||
|     // Assert that we're on the selection step | ||||
|     await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled() | ||||
|     // Select a face | ||||
|     await page.mouse.move(700, 200) | ||||
|     await page.mouse.click(700, 200) | ||||
|  | ||||
|     // Assert that we're on the distance step | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'distance', exact: false }) | ||||
|     ).toBeDisabled() | ||||
|  | ||||
|     // Assert that the an alternative variable name is chosen, | ||||
|     // since the default variable name is already in use (distance) | ||||
|     await page.getByRole('button', { name: 'Create new variable' }).click() | ||||
|     await expect(page.getByPlaceholder('Variable name')).toHaveValue( | ||||
|       'distance001' | ||||
|     ) | ||||
|  | ||||
|     const continueButton = page.getByRole('button', { name: 'Continue' }) | ||||
|     const submitButton = page.getByRole('button', { name: 'Submit command' }) | ||||
|     await continueButton.click() | ||||
|  | ||||
|     // Review step and argument hotkeys | ||||
|     await expect(submitButton).toBeEnabled() | ||||
|     await expect(submitButton).toBeFocused() | ||||
|     await submitButton.press('Backspace') | ||||
|  | ||||
|     // Assert we're back on the distance step | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'distance', exact: false }) | ||||
|     ).toBeDisabled() | ||||
|  | ||||
|     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( | ||||
|       `const distance = sqrt(20) | ||||
| const distance001 = ${KCL_DEFAULT_LENGTH} | ||||
| const 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 extrude001 = extrude(distance001, sketch001)`.replace( | ||||
|         /(\r\n|\n|\r)/gm, | ||||
|         '' | ||||
|       ) // remove newlines | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   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() | ||||
|  | ||||
|     const sketchButton = page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     const cmdBarButton = page.getByRole('button', { name: 'Commands' }) | ||||
|     const rectangleToolCommand = page.getByRole('option', { | ||||
|       name: 'rectangle', | ||||
|     }) | ||||
|     const rectangleToolButton = page.getByRole('button', { | ||||
|       name: 'Corner rectangle', | ||||
|       exact: true, | ||||
|     }) | ||||
|     const lineToolCommand = page.getByRole('option', { | ||||
|       name: 'Line', | ||||
|     }) | ||||
|     const lineToolButton = page.getByRole('button', { | ||||
|       name: 'Line', | ||||
|       exact: true, | ||||
|     }) | ||||
|     const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' }) | ||||
|     const arcToolButton = page.getByRole('button', { | ||||
|       name: 'Tangential Arc', | ||||
|       exact: true, | ||||
|     }) | ||||
|  | ||||
|     // Start a sketch | ||||
|     await sketchButton.click() | ||||
|     await page.mouse.click(700, 200) | ||||
|  | ||||
|     // Switch between sketch tools via the command bar | ||||
|     await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true') | ||||
|     await cmdBarButton.click() | ||||
|     await rectangleToolCommand.click() | ||||
|     await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true') | ||||
|     await cmdBarButton.click() | ||||
|     await lineToolCommand.click() | ||||
|     await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true') | ||||
|  | ||||
|     // Click in the scene a couple times to draw a line | ||||
|     // so tangential arc is valid | ||||
|     await page.mouse.click(700, 200) | ||||
|     await page.mouse.move(700, 300, { steps: 5 }) | ||||
|     await page.mouse.click(700, 300) | ||||
|  | ||||
|     // switch to tangential arc via command bar | ||||
|     await cmdBarButton.click() | ||||
|     await arcToolCommand.click() | ||||
|     await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true') | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										519
									
								
								e2e/playwright/copilot-ghost-test.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,519 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
| test.describe('Copilot ghost text', () => { | ||||
|   test.skip(true, 'Needs to get covered again') | ||||
|  | ||||
|   test('completes code in empty file', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|  | ||||
|     // We should be able to hit Tab to accept the completion. | ||||
|     await page.keyboard.press('Tab') | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|  | ||||
|     // Hit enter a few times. | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)    ` | ||||
|     ) | ||||
|  | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test.skip('copilot disabled in sketch mode no select plane', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     // Click sketch mode. | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(500) | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     // Exit sketch mode. | ||||
|     await page.getByRole('button', { name: 'Exit Sketch' }).click() | ||||
|  | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|  | ||||
|     // We should be able to hit Tab to accept the completion. | ||||
|     await page.keyboard.press('Tab') | ||||
|     await expect(page.locator('.cm-content')).toContainText( | ||||
|       `fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('copilot disabled in sketch mode after selecting plane', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     // Click sketch mode. | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
|     await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|  | ||||
|     // select a plane | ||||
|     await page.mouse.click(700, 200) | ||||
|     await page.waitForTimeout(700) // wait for animation | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(500) | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')` | ||||
|     ) | ||||
|  | ||||
|     // Escape to exit the tool. | ||||
|     await u.openDebugPanel() | ||||
|     await u.closeDebugPanel() | ||||
|     await page.keyboard.press('Escape') | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(500) | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')` | ||||
|     ) | ||||
|  | ||||
|     // Escape again to exit sketch mode. | ||||
|     await u.openDebugPanel() | ||||
|     await u.closeDebugPanel() | ||||
|     await page.keyboard.press('Escape') | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|  | ||||
|     // We should be able to hit Tab to accept the completion. | ||||
|     await page.keyboard.press('Tab') | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|  | ||||
|     // Hit enter a few times. | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)    ` | ||||
|     ) | ||||
|  | ||||
|     await expect(page.locator('.cm-ghostText')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('ArrowUp 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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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('ArrowUp') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('ArrowDown 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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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('ArrowDown') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('ArrowLeft 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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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('ArrowLeft') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('ArrowRight 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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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('ArrowRight') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('Enter in code scoots it down', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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('Enter') | ||||
|     await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Ctrl+shift+z 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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control' | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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.down(CtrlKey) | ||||
|     await page.keyboard.down('Shift') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|     await page.keyboard.up('Shift') | ||||
|     await expect(page.locator('.cm-ghostText').first()).not.toBeVisible() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
|  | ||||
|   test('Ctrl+z in code rejects the suggestion and undos the last code', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control' | ||||
|  | ||||
|     await page.waitForTimeout(800) | ||||
|     await u.codeLocator.click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     await page.keyboard.type('{thing: "blah"}', { delay: 0 }) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`) | ||||
|  | ||||
|     // We wanna make sure the code saves. | ||||
|     await page.waitForTimeout(800) | ||||
|  | ||||
|     // Ctrl+z | ||||
|     await page.keyboard.down(CtrlKey) | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|  | ||||
|     // Ctrl+shift+z | ||||
|     await page.keyboard.down(CtrlKey) | ||||
|     await page.keyboard.down('Shift') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|     await page.keyboard.up('Shift') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`) | ||||
|  | ||||
|     // We wanna make sure the code saves. | ||||
|     await page.waitForTimeout(800) | ||||
|  | ||||
|     await page.waitForTimeout(500) | ||||
|     await page.keyboard.press('Enter') | ||||
|     await expect(page.locator('.cm-ghostText').first()).toBeVisible() | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `{thing: "blah"}fn cube = (pos, scale) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const part001 = cube([0,0], 20)    |> close(%)    |> extrude(20, %)` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-ghostText').first()).toHaveText( | ||||
|       `fn cube = (pos, scale) => {` | ||||
|     ) | ||||
|  | ||||
|     // Once for the enter. | ||||
|     await page.keyboard.down(CtrlKey) | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|  | ||||
|     // Once for the text. | ||||
|     await page.keyboard.down(CtrlKey) | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|  | ||||
|     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(``) | ||||
|   }) | ||||
|  | ||||
|   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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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() | ||||
|  | ||||
|     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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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() | ||||
|  | ||||
|     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 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     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) => {  const sg = startSketchOn('XY')    |> startProfileAt(pos, %)    |> line([0, scale], %)    |> line([scale, 0], %)    |> line([0, -scale], %)  return sg}const 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() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										867
									
								
								e2e/playwright/editor-tests.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,867 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { uuidv4 } from 'lib/utils' | ||||
| import { getUtils, setup, tearDown } from './test-utils' | ||||
|  | ||||
| test.beforeEach(async ({ context, page }) => { | ||||
|   await setup(context, page) | ||||
| }) | ||||
|  | ||||
| test.afterEach(async ({ page }, testInfo) => { | ||||
|   await tearDown(page, testInfo) | ||||
| }) | ||||
|  | ||||
| test.describe('Editor tests', () => { | ||||
|   test('can comment out code with ctrl+/', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control' | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type(`const sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)`) | ||||
|  | ||||
|     await page.keyboard.down(CtrlKey) | ||||
|     await page.keyboard.press('/') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     // |> close(%)`) | ||||
|  | ||||
|     // uncomment the code | ||||
|     await page.keyboard.down(CtrlKey) | ||||
|     await page.keyboard.press('/') | ||||
|     await page.keyboard.up(CtrlKey) | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const 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', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type(`const 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(`const sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   test('fold gutters work', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     const fullCode = `const sketch001 = startSketchOn('XY') | ||||
|      |> startProfileAt([-10, -10], %) | ||||
|      |> line([20, 0], %) | ||||
|      |> line([0, 20], %) | ||||
|      |> line([-20, 0], %) | ||||
|      |> close(%)` | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XY') | ||||
|      |> startProfileAt([-10, -10], %) | ||||
|      |> line([20, 0], %) | ||||
|      |> line([0, 20], %) | ||||
|      |> line([-20, 0], %) | ||||
|      |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // TODO: Jess needs to fix this but you have to mod the code to get them to show | ||||
|     // up, its an annoying codemirror thing. | ||||
|     await page.locator('.cm-content').click() | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     const foldGutterFoldLine = page.locator('[title="Fold line"]') | ||||
|     const foldGutterUnfoldLine = page.locator('[title="Unfold line"]') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(fullCode) | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // Make sure we have a fold gutter | ||||
|     await expect(foldGutterFoldLine).toBeVisible() | ||||
|     await expect(foldGutterUnfoldLine).not.toBeVisible() | ||||
|  | ||||
|     // Collapse the code | ||||
|     await foldGutterFoldLine.click() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XY')…   ` | ||||
|     ) | ||||
|     await expect(page.locator('.cm-content')).not.toHaveText(fullCode) | ||||
|     await expect(foldGutterFoldLine).not.toBeVisible() | ||||
|     await expect(foldGutterUnfoldLine.nth(1)).toBeVisible() | ||||
|  | ||||
|     // Expand the code | ||||
|     await foldGutterUnfoldLine.nth(1).click() | ||||
|     await expect(page.locator('.cm-content')).toHaveText(fullCode) | ||||
|  | ||||
|     // Delete all the code. | ||||
|     await page.locator('.cm-content').click() | ||||
|     // Select all | ||||
|     await page.keyboard.press('Control+A') | ||||
|     await page.keyboard.press('Backspace') | ||||
|     await page.keyboard.press('Meta+A') | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText(``) | ||||
|     await expect(page.locator('.cm-content')).not.toHaveText(fullCode) | ||||
|  | ||||
|     await expect(foldGutterUnfoldLine).not.toBeVisible() | ||||
|     await expect(foldGutterFoldLine).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('hover over functions shows function description', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // focus the editor | ||||
|     await u.codeLocator.click() | ||||
|  | ||||
|     // Hover over  the startSketchOn function | ||||
|     await page.getByText('startSketchOn').hover() | ||||
|     await expect(page.locator('.hover-tooltip')).toBeVisible() | ||||
|     await expect( | ||||
|       page.getByText( | ||||
|         'Start a new 2-dimensional sketch on a specific plane or face' | ||||
|       ) | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     // Hover over the line function | ||||
|     await page.getByText('line').first().hover() | ||||
|     await expect(page.locator('.hover-tooltip')).toBeVisible() | ||||
|     await expect(page.getByText('Draw a line')).toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('if you use the format keyboard binding it formats your code', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XY') | ||||
|   |> startProfileAt([-10, -10], %) | ||||
|   |> line([20, 0], %) | ||||
|   |> line([0, 20], %) | ||||
|   |> line([-20, 0], %) | ||||
|   |> close(%)` | ||||
|       ) | ||||
|       localStorage.setItem('disableAxis', 'true') | ||||
|     }) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // focus the editor | ||||
|     await u.codeLocator.click() | ||||
|  | ||||
|     // Hit alt+shift+f to format the code | ||||
|     await page.keyboard.press('Alt+Shift+KeyF') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XY') | ||||
|     |> startProfileAt([-10, -10], %) | ||||
|     |> line([20, 0], %) | ||||
|     |> line([0, 20], %) | ||||
|     |> line([-20, 0], %) | ||||
|     |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   test('if you write kcl with lint errors you get lints', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type('const my_snake_case_var = 5') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type('const myCamelCaseVar = 5') | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     // press arrows to clear autocomplete | ||||
|     await page.keyboard.press('ArrowLeft') | ||||
|     await page.keyboard.press('ArrowRight') | ||||
|  | ||||
|     // error in guter | ||||
|     await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() | ||||
|  | ||||
|     // error text on hover | ||||
|     await page.hover('.cm-lint-marker-info') | ||||
|     await expect( | ||||
|       page.getByText('Identifiers must be lowerCamelCase').first() | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     // select the line that's causing the error and delete it | ||||
|     await page.getByText('const my_snake_case_var = 5').click() | ||||
|     await page.keyboard.press('End') | ||||
|     await page.keyboard.down('Shift') | ||||
|     await page.keyboard.press('Home') | ||||
|     await page.keyboard.up('Shift') | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     // wait for .cm-lint-marker-info not to be visible | ||||
|     await expect(page.locator('.cm-lint-marker-info')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('if you fixup kcl errors you clear lints', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XZ') | ||||
|   |> startProfileAt([3.29, 7.86], %) | ||||
|   |> line([2.48, 2.44], %) | ||||
|   |> line([2.66, 1.17], %) | ||||
|   |> close(%) | ||||
|   ` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     await u.codeLocator.click() | ||||
|  | ||||
|     await page.getByText(' |> line([2.48, 2.44], %)').click() | ||||
|  | ||||
|     await expect( | ||||
|       page.locator('.cm-lint-marker-error').first() | ||||
|     ).not.toBeVisible() | ||||
|     await page.keyboard.press('End') | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() | ||||
|     await page.keyboard.type(')') | ||||
|     await expect( | ||||
|       page.locator('.cm-lint-marker-error').first() | ||||
|     ).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('if you write invalid kcl you get inlined errors', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.setViewportSize({ width: 1000, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     /* add the following code to the editor ($ error is not a valid line) | ||||
|       $ error | ||||
|       const topAng = 30 | ||||
|       const bottomAng = 25 | ||||
|      */ | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.type('$ error') | ||||
|  | ||||
|     // press arrows to clear autocomplete | ||||
|     await page.keyboard.press('ArrowLeft') | ||||
|     await page.keyboard.press('ArrowRight') | ||||
|  | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type('const topAng = 30') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type('const bottomAng = 25') | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     // error in guter | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|  | ||||
|     // error text on hover | ||||
|     await page.hover('.cm-lint-marker-error') | ||||
|     await expect(page.getByText('Unexpected token').first()).toBeVisible() | ||||
|  | ||||
|     // select the line that's causing the error and delete it | ||||
|     await page.getByText('$ error').click() | ||||
|     await page.keyboard.press('End') | ||||
|     await page.keyboard.down('Shift') | ||||
|     await page.keyboard.press('Home') | ||||
|     await page.keyboard.up('Shift') | ||||
|     await page.keyboard.press('Backspace') | ||||
|  | ||||
|     // wait for .cm-lint-marker-error not to be visible | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // let's check we get an error when defining the same variable twice | ||||
|     await page.getByText('const bottomAng = 25').click() | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type("// Let's define the same thing twice") | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type('const topAng = 42') | ||||
|     await page.keyboard.press('ArrowLeft') | ||||
|  | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|     await expect( | ||||
|       page.locator('.cm-lint-marker.cm-lint-marker-error') | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     await page.locator('.cm-lint-marker.cm-lint-marker-error').hover() | ||||
|     await expect(page.locator('.cm-diagnosticText').first()).toBeVisible() | ||||
|     await expect( | ||||
|       page.getByText('Cannot redefine `topAng`').first() | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     const secondTopAng = page.getByText('topAng').first() | ||||
|     await secondTopAng?.dblclick() | ||||
|     await page.keyboard.type('otherAng') | ||||
|  | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|   }) | ||||
|  | ||||
|   test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const length = .750 | ||||
|   const width = 0.500 | ||||
|   const height = 0.500 | ||||
|   const dia = 4 | ||||
|    | ||||
|   fn squareHole = (l, w) => { | ||||
|     const 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 u.waitForAuthSkipAppStart() | ||||
|  | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.closeDebugPanel() | ||||
|  | ||||
|     // check no error to begin with | ||||
|     await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() | ||||
|  | ||||
|     // Click on the bottom of the code editor to add a new line | ||||
|     await u.codeLocator.click() | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('ArrowDown') | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.keyboard.type(`const extrusion = startSketchOn('XY') | ||||
|     |> circle([0, 0], dia/2, %) | ||||
|   |> hole(squareHole(length, width, height), %) | ||||
|   |> extrude(height, %)`) | ||||
|  | ||||
|     // error in gutter | ||||
|     await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() | ||||
|     await page.hover('.cm-lint-marker-error:first-child') | ||||
|     await expect( | ||||
|       page.getByText('Expected 2 arguments, got 3').first() | ||||
|     ).toBeVisible() | ||||
|  | ||||
|     // Make sure there are two diagnostics | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2) | ||||
|   }) | ||||
|   test('if your kcl gets an error from the engine it is inlined', async ({ | ||||
|     page, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const box = startSketchOn('XY') | ||||
|   |> startProfileAt([0, 0], %) | ||||
|   |> line([0, 10], %) | ||||
|   |> line([10, 0], %) | ||||
|   |> line([0, -10], %, $revolveAxis) | ||||
|   |> close(%) | ||||
|   |> extrude(10, %) | ||||
|  | ||||
|   const 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.goto('/') | ||||
|     await u.waitForPageLoad() | ||||
|  | ||||
|     await expect(page.locator('.cm-lint-marker-error')).toBeVisible() | ||||
|  | ||||
|     // error text on hover | ||||
|     await page.hover('.cm-lint-marker-error') | ||||
|     const searchText = | ||||
|       'sketch profile must lie entirely on one side of the revolution axis' | ||||
|     await expect(page.getByText(searchText)).toBeVisible() | ||||
|   }) | ||||
|   test.describe('Autocomplete works', () => { | ||||
|     test('with enter/click to accept the completion', async ({ page }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|       // this test might be brittle as we add and remove functions | ||||
|       // but should also be easy to update. | ||||
|       // tests clicking on an option, selection the first option | ||||
|       // and arrowing down to an option | ||||
|  | ||||
|       await u.codeLocator.click() | ||||
|       await page.keyboard.type('const sketch001 = start') | ||||
|  | ||||
|       // expect there to be six auto complete options | ||||
|       await expect(page.locator('.cm-completionLabel')).toHaveCount(8) | ||||
|       // this makes sure we can accept a completion with click | ||||
|       await page.getByText('startSketchOn').click() | ||||
|       await page.keyboard.type("'XZ'") | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.keyboard.type('  |> startProfi') | ||||
|       // expect there be a single auto complete option that we can just hit enter on | ||||
|       await expect(page.locator('.cm-completionLabel')).toBeVisible() | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Enter') // accepting the auto complete, not a new line | ||||
|  | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.type('12') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.type('  |> lin') | ||||
|  | ||||
|       await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible() | ||||
|       await page.waitForTimeout(100) | ||||
|       // press arrow down twice then enter to accept xLine | ||||
|       await page.keyboard.press('ArrowDown') | ||||
|       await page.keyboard.press('ArrowDown') | ||||
|       await page.keyboard.press('Enter') | ||||
|       // finish line with comment | ||||
|       await page.keyboard.type('5') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|  | ||||
|       await page.keyboard.type(' // ') | ||||
|       // Since we need to parse the ast to know we are in a comment we gotta hang tight. | ||||
|       await page.waitForTimeout(700) | ||||
|       await page.keyboard.type('lin ') | ||||
|       await page.waitForTimeout(200) | ||||
|       // there shouldn't be any auto complete options for 'lin' in the comment | ||||
|       await expect(page.locator('.cm-completionLabel')).not.toBeVisible() | ||||
|  | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([3.14, 12], %) | ||||
|     |> xLine(5, %) // lin`) | ||||
|     }) | ||||
|  | ||||
|     test('with tab to accept the completion', async ({ page }) => { | ||||
|       const u = await getUtils(page) | ||||
|       // const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|       await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|       await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|       // this test might be brittle as we add and remove functions | ||||
|       // but should also be easy to update. | ||||
|       // tests clicking on an option, selection the first option | ||||
|       // and arrowing down to an option | ||||
|  | ||||
|       await u.codeLocator.click() | ||||
|       await page.keyboard.type('const sketch001 = startSketchO') | ||||
|       await page.waitForTimeout(100) | ||||
|  | ||||
|       // Make sure just hitting tab will take the only one left | ||||
|       await expect(page.locator('.cm-completionLabel')).toHaveCount(1) | ||||
|       await page.waitForTimeout(500) | ||||
|       await page.keyboard.press('ArrowDown') | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(500) | ||||
|       await page.keyboard.type("'XZ'") | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.keyboard.type('  |> startProfi') | ||||
|       // expect there be a single auto complete option that we can just hit enter on | ||||
|       await expect(page.locator('.cm-completionLabel')).toBeVisible() | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') // accepting the auto complete, not a new line | ||||
|  | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.keyboard.type('12') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Enter') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.type('  |> lin') | ||||
|  | ||||
|       await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible() | ||||
|       await page.waitForTimeout(100) | ||||
|       // press arrow down twice then tab to accept xLine | ||||
|       await page.keyboard.press('ArrowDown') | ||||
|       await page.keyboard.press('ArrowDown') | ||||
|       await page.keyboard.press('Tab') | ||||
|       // finish line with comment | ||||
|       await page.keyboard.type('5') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|       await page.waitForTimeout(100) | ||||
|       await page.keyboard.press('Tab') | ||||
|  | ||||
|       await page.keyboard.type(' // ') | ||||
|       // Since we need to parse the ast to know we are in a comment we gotta hang tight. | ||||
|       await page.waitForTimeout(700) | ||||
|       await page.keyboard.type('lin ') | ||||
|       await page.waitForTimeout(200) | ||||
|       // there shouldn't be any auto complete options for 'lin' in the comment | ||||
|       await expect(page.locator('.cm-completionLabel')).not.toBeVisible() | ||||
|  | ||||
|       await expect(page.locator('.cm-content')) | ||||
|         .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([3.14, 12], %) | ||||
|     |> xLine(5, %) // lin`) | ||||
|     }) | ||||
|   }) | ||||
|   test('Can undo a click and point extrude with ctrl+z', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
|  | ||||
|     await page.waitForTimeout(100) | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await u.sendCustomCmd({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_look_at', | ||||
|         vantage: { x: 0, y: -1250, z: 580 }, | ||||
|         center: { x: 0, y: 0, z: 0 }, | ||||
|         up: { x: 0, y: 0, z: 1 }, | ||||
|       }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|     await u.sendCustomCmd({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_get_settings', | ||||
|       }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     const startPX = [665, 458] | ||||
|  | ||||
|     const dragPX = 40 | ||||
|  | ||||
|     await page.getByText('startProfileAt([4.61, -14.01], %)').click() | ||||
|     await expect(page.getByRole('button', { name: 'Extrude' })).toBeVisible() | ||||
|     await page.getByRole('button', { name: 'Extrude' }).click() | ||||
|  | ||||
|     await expect(page.getByTestId('command-bar')).toBeVisible() | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.getByText('Confirm Extrude')).toBeVisible() | ||||
|     await page.keyboard.press('Enter') | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     // expect the code to have changed | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `const sketch001 = startSketchOn('XZ')  |> startProfileAt([4.61, -14.01], %)  |> line([12.73, -0.09], %)  |> tangentialArcTo([24.95, -5.38], %)  |> close(%)const extrude001 = extrude(5, sketch001)` | ||||
|     ) | ||||
|  | ||||
|     // Now hit undo | ||||
|     await page.keyboard.down('Control') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up('Control') | ||||
|  | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%)`) | ||||
|   }) | ||||
|  | ||||
|   // failing for the same reason as "Can edit a sketch that has been extruded in the same pipe" | ||||
|   // please fix together | ||||
|   test.fixme('Can undo a sketch modification with ctrl+z', async ({ page }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|  | ||||
|     await u.waitForAuthSkipAppStart() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Start Sketch' }) | ||||
|     ).not.toBeDisabled() | ||||
|  | ||||
|     await page.waitForTimeout(100) | ||||
|     await u.openAndClearDebugPanel() | ||||
|     await u.sendCustomCmd({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_look_at', | ||||
|         vantage: { x: 0, y: -1250, z: 580 }, | ||||
|         center: { x: 0, y: 0, z: 0 }, | ||||
|         up: { x: 0, y: 0, z: 1 }, | ||||
|       }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|     await u.sendCustomCmd({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'default_camera_get_settings', | ||||
|       }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|  | ||||
|     const startPX = [665, 458] | ||||
|  | ||||
|     const dragPX = 40 | ||||
|  | ||||
|     await page.getByText('startProfileAt([4.61, -14.01], %)').click() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Edit Sketch' }) | ||||
|     ).toBeVisible() | ||||
|     await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|     await page.waitForTimeout(400) | ||||
|     let prevContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|     await expect(page.getByTestId('segment-overlay')).toHaveCount(2) | ||||
|  | ||||
|     // drag startProfieAt handle | ||||
|     await page.dragAndDrop('#stream', '#stream', { | ||||
|       sourcePosition: { x: startPX[0], y: startPX[1] }, | ||||
|       targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||
|     prevContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|     // drag line handle | ||||
|     // we wait so it saves the code | ||||
|     await page.waitForTimeout(800) | ||||
|  | ||||
|     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 }, | ||||
|     }) | ||||
|     await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||
|     prevContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|     // we wait so it saves the code | ||||
|     await page.waitForTimeout(800) | ||||
|  | ||||
|     // drag tangentialArcTo handle | ||||
|     const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]') | ||||
|     await page.dragAndDrop('#stream', '#stream', { | ||||
|       sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 }, | ||||
|       targetPosition: { | ||||
|         x: tangentEnd.x + dragPX, | ||||
|         y: tangentEnd.y + dragPX, | ||||
|       }, | ||||
|     }) | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')).not.toHaveText(prevContent) | ||||
|  | ||||
|     // expect the code to have changed | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([7.12, -16.82], %) | ||||
|     |> line([15.4, -2.74], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> line([2.65, -2.69], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)`) | ||||
|  | ||||
|     // Hit undo | ||||
|     await page.keyboard.down('Control') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up('Control') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([7.12, -16.82], %) | ||||
|     |> line([15.4, -2.74], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)`) | ||||
|  | ||||
|     // Hit undo again. | ||||
|     await page.keyboard.down('Control') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up('Control') | ||||
|  | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([7.12, -16.82], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)`) | ||||
|  | ||||
|     // Hit undo again. | ||||
|     await page.keyboard.down('Control') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up('Control') | ||||
|  | ||||
|     await page.waitForTimeout(100) | ||||
|     await expect(page.locator('.cm-content')) | ||||
|       .toHaveText(`const sketch001 = startSketchOn('XZ') | ||||
|     |> startProfileAt([4.61, -14.01], %) | ||||
|     |> line([12.73, -0.09], %) | ||||
|     |> tangentialArcTo([24.95, -5.38], %) | ||||
|     |> close(%) | ||||
|     |> extrude(5, %)`) | ||||
|   }) | ||||
| }) | ||||
| Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 49 KiB | 
