Pass various.spec.ts, by far the hardest one
This commit is contained in:
		@ -1,29 +1,20 @@
 | 
			
		||||
import { test, expect, Page } from '@playwright/test'
 | 
			
		||||
import { test, expect, Page } from './zoo-test'
 | 
			
		||||
import {
 | 
			
		||||
  getUtils,
 | 
			
		||||
  TEST_COLORS,
 | 
			
		||||
  setup,
 | 
			
		||||
  tearDown,
 | 
			
		||||
  commonPoints,
 | 
			
		||||
  PERSIST_MODELING_CONTEXT,
 | 
			
		||||
} from './test-utils'
 | 
			
		||||
 | 
			
		||||
test.beforeEach(async ({ context, page }, testInfo) => {
 | 
			
		||||
  await setup(context, page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.afterEach(async ({ page }, testInfo) => {
 | 
			
		||||
  await tearDown(page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
import { HomePageFixture } from './fixtures/homePageFixture'
 | 
			
		||||
 | 
			
		||||
test.setTimeout(120000)
 | 
			
		||||
 | 
			
		||||
async function doBasicSketch(page: Page, openPanes: string[]) {
 | 
			
		||||
async function doBasicSketch(page: Page, homePage: HomePageFixture, openPanes: string[]) {
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
  await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
  const PUR = 400 / 37.5 //pixeltoUnitRatio
 | 
			
		||||
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
  await homePage.goToModelingScene()
 | 
			
		||||
  await u.openDebugPanel()
 | 
			
		||||
 | 
			
		||||
  // If we have the code pane open, we should see the code.
 | 
			
		||||
@ -148,20 +139,16 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test.describe('Basic sketch', () => {
 | 
			
		||||
  test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => {
 | 
			
		||||
    // Skip on windows it is being weird.
 | 
			
		||||
    test.skip(process.platform === 'win32', 'Skip on windows')
 | 
			
		||||
    await doBasicSketch(page, ['code'])
 | 
			
		||||
  })
 | 
			
		||||
  test('code pane open at start', { tag: ['@skipWin'] }, async ({ page, homePage }) => { // Skip on windows it is being weird.
 | 
			
		||||
  test.skip(process.platform === 'win32', 'Skip on windows')
 | 
			
		||||
  await doBasicSketch(page, homePage, ['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, [])
 | 
			
		||||
  })
 | 
			
		||||
  test('code pane closed at start', async ({ page, homePage }) => { // Load the app with the code panes
 | 
			
		||||
  await page.addInitScript(async (persistModelingContext) => {
 | 
			
		||||
    localStorage.setItem(
 | 
			
		||||
      persistModelingContext,
 | 
			
		||||
      JSON.stringify({ openPanes: [] })
 | 
			
		||||
    )
 | 
			
		||||
  }, PERSIST_MODELING_CONTEXT)
 | 
			
		||||
  await doBasicSketch(page, homePage, []) })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { test, expect } from '@playwright/test'
 | 
			
		||||
import { test, expect } from './zoo-test'
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  getUtils,
 | 
			
		||||
  setup,
 | 
			
		||||
  setupElectron,
 | 
			
		||||
  tearDown,
 | 
			
		||||
  executorInputPath,
 | 
			
		||||
} from './test-utils'
 | 
			
		||||
import { join } from 'path'
 | 
			
		||||
@ -12,261 +9,234 @@ import { bracket } from 'lib/exampleKcl'
 | 
			
		||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
 | 
			
		||||
import fsp from 'fs/promises'
 | 
			
		||||
 | 
			
		||||
test.beforeEach(async ({ context, page }, testInfo) => {
 | 
			
		||||
  await setup(context, page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.afterEach(async ({ page }, testInfo) => {
 | 
			
		||||
  await tearDown(page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.describe('Code pane and errors', () => {
 | 
			
		||||
  test('Typing KCL errors induces a badge on the code pane button', async ({
 | 
			
		||||
    page,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
  test('Typing KCL errors induces a badge on the code pane button', async ({ page,  homePage }) => { const u = await getUtils(page)
 | 
			
		||||
  
 | 
			
		||||
  // Load the app with the working starter code
 | 
			
		||||
  await page.addInitScript(() => {
 | 
			
		||||
    localStorage.setItem(
 | 
			
		||||
      'persistCode',
 | 
			
		||||
      `// Extruded Triangle
 | 
			
		||||
  sketch001 = startSketchOn('XZ')
 | 
			
		||||
    |> startProfileAt([0, 0], %)
 | 
			
		||||
    |> line([10, 0], %)
 | 
			
		||||
    |> line([-5, 10], %)
 | 
			
		||||
    |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
  extrude001 = extrude(5, sketch001)`
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
  
 | 
			
		||||
  await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
  await homePage.goToModelingScene()
 | 
			
		||||
  
 | 
			
		||||
  // 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') })
 | 
			
		||||
 | 
			
		||||
    // Load the app with the working starter code
 | 
			
		||||
    await page.addInitScript(() => {
 | 
			
		||||
      localStorage.setItem(
 | 
			
		||||
        'persistCode',
 | 
			
		||||
        `// Extruded Triangle
 | 
			
		||||
sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
  |> line([10, 0], %)
 | 
			
		||||
  |> line([-5, 10], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
extrude001 = extrude(5, sketch001)`
 | 
			
		||||
  test('Opening and closing the code pane will consistently show error diagnostics', async ({ page,  homePage }) => {
 | 
			
		||||
  
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  
 | 
			
		||||
  // Load the app with the working starter code
 | 
			
		||||
  await page.addInitScript((code) => {
 | 
			
		||||
    localStorage.setItem('persistCode', code)
 | 
			
		||||
  }, bracket)
 | 
			
		||||
  
 | 
			
		||||
  await page.setBodyDimensions({ width: 1200, height: 900 })
 | 
			
		||||
  await homePage.goToModelingScene()
 | 
			
		||||
  
 | 
			
		||||
  // 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('thickness, bracketLeg1Sketch)').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.locator('.cm-tooltip').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.locator('.cm-tooltip').first()).toBeVisible() })
 | 
			
		||||
 | 
			
		||||
  test('When error is not in view you can click the badge to scroll to it', async ({ page,  homePage, context }) => { const u = await getUtils(page)
 | 
			
		||||
  
 | 
			
		||||
  // Load the app with the working starter code
 | 
			
		||||
  await context.addInitScript((code) => {
 | 
			
		||||
    localStorage.setItem('persistCode', code)
 | 
			
		||||
  }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
 | 
			
		||||
  
 | 
			
		||||
  await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
  await homePage.goToModelingScene()
 | 
			
		||||
  
 | 
			
		||||
  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(
 | 
			
		||||
        'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed:  sketch profile must lie entirely on one side of the revolution axis" }]'
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
      .first()
 | 
			
		||||
  ).toBeVisible() })
 | 
			
		||||
 | 
			
		||||
    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,
 | 
			
		||||
  }) => {
 | 
			
		||||
    await page.goto('http://localhost:3000')
 | 
			
		||||
 | 
			
		||||
    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('thickness, bracketLeg1Sketch)').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.locator('.cm-tooltip').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.locator('.cm-tooltip').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('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()
 | 
			
		||||
  })
 | 
			
		||||
  test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ context, page,  homePage }) => { const u = await getUtils(page)
 | 
			
		||||
  
 | 
			
		||||
  // Load the app with the working starter code
 | 
			
		||||
  await context.addInitScript((code) => {
 | 
			
		||||
    localStorage.setItem('persistCode', code)
 | 
			
		||||
  }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
 | 
			
		||||
  
 | 
			
		||||
  await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
  await homePage.goToModelingScene()
 | 
			
		||||
  
 | 
			
		||||
  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('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() })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test(
 | 
			
		||||
  'Opening multiple panes persists when switching projects',
 | 
			
		||||
  { tag: '@electron' },
 | 
			
		||||
  async ({ browserName }, testInfo) => {
 | 
			
		||||
  async ({ context, browserName, page }, testInfo) => {
 | 
			
		||||
    // Setup multiple projects.
 | 
			
		||||
    const { electronApp, page } = await setupElectron({
 | 
			
		||||
      testInfo,
 | 
			
		||||
      folderSetupFn: async (dir) => {
 | 
			
		||||
        const routerTemplateDir = join(dir, 'router-template-slate')
 | 
			
		||||
        const bracketDir = join(dir, 'bracket')
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
          fsp.mkdir(routerTemplateDir, { recursive: true }),
 | 
			
		||||
          fsp.mkdir(bracketDir, { recursive: true }),
 | 
			
		||||
        ])
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
          fsp.copyFile(
 | 
			
		||||
            executorInputPath('router-template-slate.kcl'),
 | 
			
		||||
            join(routerTemplateDir, 'main.kcl')
 | 
			
		||||
          ),
 | 
			
		||||
          fsp.copyFile(
 | 
			
		||||
            executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
 | 
			
		||||
            join(bracketDir, 'main.kcl')
 | 
			
		||||
          ),
 | 
			
		||||
        ])
 | 
			
		||||
      },
 | 
			
		||||
    context.folderSetupFn(async (dir) => {
 | 
			
		||||
      const routerTemplateDir = join(dir, 'router-template-slate')
 | 
			
		||||
      const bracketDir = join(dir, 'bracket')
 | 
			
		||||
      await Promise.all([
 | 
			
		||||
        fsp.mkdir(routerTemplateDir, { recursive: true }),
 | 
			
		||||
        fsp.mkdir(bracketDir, { recursive: true }),
 | 
			
		||||
      ])
 | 
			
		||||
      await Promise.all([
 | 
			
		||||
        fsp.copyFile(
 | 
			
		||||
          executorInputPath('router-template-slate.kcl'),
 | 
			
		||||
          join(routerTemplateDir, 'main.kcl')
 | 
			
		||||
        ),
 | 
			
		||||
        fsp.copyFile(
 | 
			
		||||
          executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
 | 
			
		||||
          join(bracketDir, 'main.kcl')
 | 
			
		||||
        ),
 | 
			
		||||
      ])
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
    await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
    await test.step('Opening the bracket project should load', async () => {
 | 
			
		||||
      await expect(page.getByText('bracket')).toBeVisible()
 | 
			
		||||
@ -309,30 +279,21 @@ test(
 | 
			
		||||
      await expect(page.locator('#variables-pane')).toBeVisible()
 | 
			
		||||
      await expect(page.locator('#logs-pane')).toBeVisible()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await electronApp.close()
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
test(
 | 
			
		||||
  'external change of file contents are reflected in editor',
 | 
			
		||||
  { tag: '@electron' },
 | 
			
		||||
  async ({ browserName }, testInfo) => {
 | 
			
		||||
  async ({ context, browserName, page }, testInfo) => {
 | 
			
		||||
    const PROJECT_DIR_NAME = 'lee-was-here'
 | 
			
		||||
    const {
 | 
			
		||||
      electronApp,
 | 
			
		||||
      page,
 | 
			
		||||
      dir: projectsDir,
 | 
			
		||||
    } = await setupElectron({
 | 
			
		||||
      testInfo,
 | 
			
		||||
      folderSetupFn: async (dir) => {
 | 
			
		||||
        const aProjectDir = join(dir, PROJECT_DIR_NAME)
 | 
			
		||||
        await fsp.mkdir(aProjectDir, { recursive: true })
 | 
			
		||||
      },
 | 
			
		||||
    const { dir: projectsDir, } = await context.folderSetupFn(async (dir) => {
 | 
			
		||||
      const aProjectDir = join(dir, PROJECT_DIR_NAME)
 | 
			
		||||
      await fsp.mkdir(aProjectDir, { recursive: true })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
    await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
    await test.step('Open the project', async () => {
 | 
			
		||||
      await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
 | 
			
		||||
@ -351,7 +312,5 @@ test(
 | 
			
		||||
      )
 | 
			
		||||
      await u.editorTextMatches(content)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await electronApp.close()
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -65,6 +65,7 @@ export class AuthenticatedTronApp {
 | 
			
		||||
  public readonly testInfo: TestInfo
 | 
			
		||||
  public electronApp?: ElectronApplication
 | 
			
		||||
  public readonly viewPortSize = { width: 1200, height: 500 }
 | 
			
		||||
  public dir: string = ""
 | 
			
		||||
 | 
			
		||||
  constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
 | 
			
		||||
    this._page = page
 | 
			
		||||
@ -80,7 +81,7 @@ export class AuthenticatedTronApp {
 | 
			
		||||
      appSettings?: Partial<SaveSettingsPayload>
 | 
			
		||||
    } = { fixtures: {} }
 | 
			
		||||
  ) {
 | 
			
		||||
    const { electronApp, page, context } = await setupElectron({
 | 
			
		||||
    const { electronApp, page, context, dir } = await setupElectron({
 | 
			
		||||
      testInfo: this.testInfo,
 | 
			
		||||
      folderSetupFn: arg.folderSetupFn,
 | 
			
		||||
      cleanProjectDir: arg.cleanProjectDir,
 | 
			
		||||
@ -89,6 +90,7 @@ export class AuthenticatedTronApp {
 | 
			
		||||
    this.page = page
 | 
			
		||||
    this.context = context
 | 
			
		||||
    this.electronApp = electronApp
 | 
			
		||||
    this.dir = dir
 | 
			
		||||
 | 
			
		||||
    // Setup localStorage, addCookies, reload
 | 
			
		||||
    await setup(this.context, this.page, this.testInfo)
 | 
			
		||||
 | 
			
		||||
@ -109,242 +109,21 @@ keychain = startSketchOn("XY")
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(thickness, %)
 | 
			
		||||
 | 
			
		||||
// generated from  /home/paultag/Downloads/zma-logomark.svg
 | 
			
		||||
fn svg = (surface, origin, depth) => {
 | 
			
		||||
  let a0 = surface |> startProfileAt([origin[0] + 45.430427, origin[1] + -14.627736], %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0, 0.764157 ],
 | 
			
		||||
    control2: [ 0, 1.528314 ],
 | 
			
		||||
    to: [ 0, 2.292469 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -3.03202, 0 ],
 | 
			
		||||
    control2: [ -6.064039, 0 ],
 | 
			
		||||
    to: [ -9.09606, 0 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0, -1.077657 ],
 | 
			
		||||
    control2: [ 0, -2.155312 ],
 | 
			
		||||
    to: [ 0, -3.232969 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.741805, 0 ],
 | 
			
		||||
    control2: [ 5.483613, 0 ],
 | 
			
		||||
    to: [ 8.225417, 0 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.740682, -2.961815 ],
 | 
			
		||||
    control2: [ -5.490342, -5.925794 ],
 | 
			
		||||
    to: [ -8.225417, -8.886255 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0, -0.723995 ],
 | 
			
		||||
    control2: [ 0, -1.447988 ],
 | 
			
		||||
    to: [ 0, -2.171981 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.712124, 0.05061 ],
 | 
			
		||||
    control2: [ 1.511636, -0.09877 ],
 | 
			
		||||
    to: [ 2.172096, 0.07005 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.68573, 0.740811 ],
 | 
			
		||||
    control2: [ 1.371459, 1.481622 ],
 | 
			
		||||
    to: [ 2.057187, 2.222436 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0, -0.76416 ],
 | 
			
		||||
    control2: [ 0, -1.52832 ],
 | 
			
		||||
    to: [ 0, -2.29248 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 3.032013, 0 ],
 | 
			
		||||
    control2: [ 6.064026, 0 ],
 | 
			
		||||
    to: [ 9.096038, 0 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0, 1.077657 ],
 | 
			
		||||
    control2: [ 0, 2.155314 ],
 | 
			
		||||
    to: [ 0, 3.232973 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.741312, 0 ],
 | 
			
		||||
    control2: [ -5.482623, 0 ],
 | 
			
		||||
    to: [ -8.223936, 0 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.741313, 2.961108 ],
 | 
			
		||||
    control2: [ 5.482624, 5.922216 ],
 | 
			
		||||
    to: [ 8.223936, 8.883325 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0, 0.724968 ],
 | 
			
		||||
    control2: [ 0, 1.449938 ],
 | 
			
		||||
    to: [ 0, 2.174907 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.712656, -0.05145 ],
 | 
			
		||||
    control2: [ -1.512554, 0.09643 ],
 | 
			
		||||
    to: [ -2.173592, -0.07298 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.685222, -0.739834 ],
 | 
			
		||||
    control2: [ -1.370445, -1.479669 ],
 | 
			
		||||
    to: [ -2.055669, -2.219505 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
    |> extrude(depth, %)
 | 
			
		||||
keychain1 = startSketchOn("XY")
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
  |> lineTo([width, 0], %)
 | 
			
		||||
  |> lineTo([width, height], %)
 | 
			
		||||
  |> lineTo([0, height], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(thickness, %)
 | 
			
		||||
 | 
			
		||||
  let a1 = surface |> startProfileAt([origin[0] + 57.920488, origin[1] + -15.244943], %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.78904, 0.106635 ],
 | 
			
		||||
    control2: [ -5.052548, -2.969529 ],
 | 
			
		||||
    to: [ -4.055141, -5.598369 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.841523, -0.918736 ],
 | 
			
		||||
    control2: [ 0.439412, -1.541892 ],
 | 
			
		||||
    to: [ -0.368488, -2.214378 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.418245, -0.448461 ],
 | 
			
		||||
    control2: [ -0.836489, -0.896922 ],
 | 
			
		||||
    to: [ -1.254732, -1.345384 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.76806, 2.995359 ],
 | 
			
		||||
    control2: [ -2.32667, 8.18409 ],
 | 
			
		||||
    to: [ 0.897655, 10.678932 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.562822, 2.186098 ],
 | 
			
		||||
    control2: [ 6.605111, 2.28043 ],
 | 
			
		||||
    to: [ 9.271202, 0.226476 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.743744, -0.797465 ],
 | 
			
		||||
    control2: [ -1.487487, -1.594932 ],
 | 
			
		||||
    to: [ -2.231232, -2.392397 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.672938, 0.421422 ],
 | 
			
		||||
    control2: [ -1.465362, 0.646946 ],
 | 
			
		||||
    to: [ -2.259264, 0.64512 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
    |> extrude(depth, %)
 | 
			
		||||
 | 
			
		||||
  let a2 = surface |> startProfileAt([origin[0] + 62.19406300000001, origin[1] + -19.500698999999997], %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.302938, 1.281141 ],
 | 
			
		||||
    control2: [ -1.53575, 2.434288 ],
 | 
			
		||||
    to: [ -0.10908, 3.279477 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.504637, 0.54145 ],
 | 
			
		||||
    control2: [ 1.009273, 1.082899 ],
 | 
			
		||||
    to: [ 1.513909, 1.624348 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.767778, -2.995425 ],
 | 
			
		||||
    control2: [ 2.327135, -8.184384 ],
 | 
			
		||||
    to: [ -0.897661, -10.679047 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.562947, -2.186022 ],
 | 
			
		||||
    control2: [ -6.604089, -2.279606 ],
 | 
			
		||||
    to: [ -9.271196, -0.227813 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.744231, 0.797952 ],
 | 
			
		||||
    control2: [ 1.488461, 1.595904 ],
 | 
			
		||||
    to: [ 2.232692, 2.393856 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.302377, -1.564629 ],
 | 
			
		||||
    control2: [ 5.793126, -0.15358 ],
 | 
			
		||||
    to: [ 6.396577, 2.547372 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.08981, 0.346302 ],
 | 
			
		||||
    control2: [ 0.134865, 0.704078 ],
 | 
			
		||||
    to: [ 0.13476, 1.061807 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
    |> extrude(depth, %)
 | 
			
		||||
 | 
			
		||||
  let a3 = surface |> startProfileAt([origin[0] + 74.124866, origin[1] + -15.244943], %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.78904, 0.106635 ],
 | 
			
		||||
    control2: [ -5.052549, -2.969529 ],
 | 
			
		||||
    to: [ -4.055142, -5.598369 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.841527, -0.918738 ],
 | 
			
		||||
    control2: [ 0.43941, -1.541892 ],
 | 
			
		||||
    to: [ -0.368497, -2.214367 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.418254, -0.448466 ],
 | 
			
		||||
    control2: [ -0.836507, -0.896931 ],
 | 
			
		||||
    to: [ -1.254761, -1.345395 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.768019, 2.995371 ],
 | 
			
		||||
    control2: [ -2.326624, 8.184088 ],
 | 
			
		||||
    to: [ 0.897678, 10.678932 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.56289, 2.186191 ],
 | 
			
		||||
    control2: [ 6.60516, 2.280307 ],
 | 
			
		||||
    to: [ 9.271371, 0.226476 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.743808, -0.797465 ],
 | 
			
		||||
    control2: [ -1.487616, -1.594932 ],
 | 
			
		||||
    to: [ -2.231424, -2.392397 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -0.672916, 0.421433 ],
 | 
			
		||||
    control2: [ -1.465344, 0.646926 ],
 | 
			
		||||
    to: [ -2.259225, 0.64512 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
    |> extrude(depth, %)
 | 
			
		||||
 | 
			
		||||
  let a4 = surface |> startProfileAt([origin[0] + 77.57333899999998, origin[1] + -16.989262999999998], %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.743298, 0.797463 ],
 | 
			
		||||
    control2: [ 1.486592, 1.594926 ],
 | 
			
		||||
    to: [ 2.229888, 2.392389 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.767827, -2.995393 ],
 | 
			
		||||
    control2: [ 2.327103, -8.184396 ],
 | 
			
		||||
    to: [ -0.897672, -10.679047 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ -2.562939, -2.186037 ],
 | 
			
		||||
    control2: [ -6.604077, -2.279589 ],
 | 
			
		||||
    to: [ -9.271185, -0.227813 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.744243, 0.797952 ],
 | 
			
		||||
    control2: [ 1.488486, 1.595904 ],
 | 
			
		||||
    to: [ 2.232729, 2.393856 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 2.302394, -1.564623 ],
 | 
			
		||||
    control2: [ 5.793201, -0.153598 ],
 | 
			
		||||
    to: [ 6.396692, 2.547372 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> bezierCurve({
 | 
			
		||||
    control1: [ 0.32074, 1.215468 ],
 | 
			
		||||
    control2: [ 0.06159, 2.564765 ],
 | 
			
		||||
    to: [ -0.690452, 3.573243 ]
 | 
			
		||||
   }, %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
    |> extrude(depth, %)
 | 
			
		||||
keychain2 = startSketchOn("XY")
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
  |> lineTo([width, 0], %)
 | 
			
		||||
  |> lineTo([width, height], %)
 | 
			
		||||
  |> lineTo([0, height], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(thickness, %)
 | 
			
		||||
 | 
			
		||||
box = startSketchOn('XY')
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
@ -354,7 +133,7 @@ box = startSketchOn('XY')
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(10, %)
 | 
			
		||||
 | 
			
		||||
  sketch001 = startSketchOn(box, revolveAxis)
 | 
			
		||||
sketch001 = startSketchOn(box, revolveAxis)
 | 
			
		||||
  |> startProfileAt([5, 10], %)
 | 
			
		||||
  |> line([0, -10], %)
 | 
			
		||||
  |> line([2, 0], %)
 | 
			
		||||
@ -364,18 +143,12 @@ box = startSketchOn('XY')
 | 
			
		||||
  axis: revolveAxis,
 | 
			
		||||
  angle: 90
 | 
			
		||||
  }, %)
 | 
			
		||||
  return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([0.0, 0.0], %)
 | 
			
		||||
  |> xLine(0.0, %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness)
 | 
			
		||||
 | 
			
		||||
startSketchOn(keychain, 'end')
 | 
			
		||||
  |> circle({ center: [
 | 
			
		||||
       width / 2,
 | 
			
		||||
       height - (keychainHoleSize + 1.5)
 | 
			
		||||
     ], radius: keychainHoleSize }, %)
 | 
			
		||||
  |> extrude(-thickness, %)`
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `thing = 1`
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import {
 | 
			
		||||
import { EngineCommand } from 'lang/std/artifactGraph'
 | 
			
		||||
import fsp from 'fs/promises'
 | 
			
		||||
import fsSync from 'fs'
 | 
			
		||||
import { join } from 'path'
 | 
			
		||||
import path from 'path'
 | 
			
		||||
import pixelMatch from 'pixelmatch'
 | 
			
		||||
import { PNG } from 'pngjs'
 | 
			
		||||
import { Protocol } from 'playwright-core/types/protocol'
 | 
			
		||||
@ -682,6 +682,29 @@ export const makeTemplate: (
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const moveDownloadedFileTo = async (page: Page, toLocation: string) => {
 | 
			
		||||
  await fsp.mkdir(path.dirname(toLocation), { recursive: true })
 | 
			
		||||
 | 
			
		||||
  const downloadDir = path.resolve(
 | 
			
		||||
    page.TEST_SETTINGS_FILE_KEY,
 | 
			
		||||
    "downloads-during-playwright"
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // Expect there to be at least one file
 | 
			
		||||
  expect.poll(async () => {
 | 
			
		||||
    const files = await fsp.readdir(downloadDir)
 | 
			
		||||
    return files.length
 | 
			
		||||
  }).toBe(1)
 | 
			
		||||
 | 
			
		||||
  // Go through the downloads dir and move files to new location
 | 
			
		||||
  const files = await fsp.readdir(downloadDir)
 | 
			
		||||
 | 
			
		||||
  // Assumption: only ever one file here.
 | 
			
		||||
  for (let file of files) {
 | 
			
		||||
    await fsp.rename(path.resolve(downloadDir, file), toLocation)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Paths {
 | 
			
		||||
  modelPath: string
 | 
			
		||||
  imagePath: string
 | 
			
		||||
@ -694,7 +717,8 @@ export const doExport = async (
 | 
			
		||||
  exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
 | 
			
		||||
): Promise<Paths> => {
 | 
			
		||||
  if (exportFrom === 'dropdown') {
 | 
			
		||||
    await page.getByRole('button', { name: APP_NAME }).click()
 | 
			
		||||
    await page.getByTestId('project-sidebar-toggle').click()
 | 
			
		||||
 | 
			
		||||
    const exportMenuButton = page.getByRole('button', {
 | 
			
		||||
      name: 'Export current part',
 | 
			
		||||
    })
 | 
			
		||||
@ -735,25 +759,12 @@ export const doExport = async (
 | 
			
		||||
  }
 | 
			
		||||
  await expect(page.getByText('Confirm Export')).toBeVisible()
 | 
			
		||||
 | 
			
		||||
  const getPromiseAndResolve = () => {
 | 
			
		||||
    let resolve: any = () => {}
 | 
			
		||||
    const promise = new Promise<Download>((r) => {
 | 
			
		||||
      resolve = r
 | 
			
		||||
    })
 | 
			
		||||
    return [promise, resolve]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
 | 
			
		||||
  let downloadCnt = 0
 | 
			
		||||
 | 
			
		||||
  if (exportFrom === 'dropdown')
 | 
			
		||||
    page.on('download', async (download) => {
 | 
			
		||||
      if (downloadCnt === 0) {
 | 
			
		||||
        downloadResolve1(download)
 | 
			
		||||
      }
 | 
			
		||||
      downloadCnt++
 | 
			
		||||
    })
 | 
			
		||||
  await page.getByRole('button', { name: 'Submit command' }).click()
 | 
			
		||||
 | 
			
		||||
  // This usually happens immediately after. If we're too slow we don't
 | 
			
		||||
  // catch it.
 | 
			
		||||
  await expect(page.getByText('Exported successfully')).toBeVisible()
 | 
			
		||||
 | 
			
		||||
  if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
 | 
			
		||||
    return {
 | 
			
		||||
      modelPath: '',
 | 
			
		||||
@ -763,15 +774,12 @@ export const doExport = async (
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Handle download
 | 
			
		||||
  const download = await downloadPromise1
 | 
			
		||||
  const downloadLocationer = (extra = '', isImage = false) =>
 | 
			
		||||
    `./e2e/playwright/export-snapshots/${output.type}-${
 | 
			
		||||
      'storage' in output ? output.storage : ''
 | 
			
		||||
    }${extra}.${isImage ? 'png' : output.type}`
 | 
			
		||||
  const downloadLocation = downloadLocationer()
 | 
			
		||||
 | 
			
		||||
  await download.saveAs(downloadLocation)
 | 
			
		||||
 | 
			
		||||
  if (output.type === 'step') {
 | 
			
		||||
    // stable timestamps for step files
 | 
			
		||||
    const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
 | 
			
		||||
@ -780,6 +788,12 @@ export const doExport = async (
 | 
			
		||||
      '1970-01-01T00:00:00.0+00:00'
 | 
			
		||||
    )
 | 
			
		||||
    await fsp.writeFile(downloadLocation, newFileContents)
 | 
			
		||||
  } else {
 | 
			
		||||
    // By default all files are downloaded to the same place in playwright
 | 
			
		||||
    // (declared in src/lib/exportSave)
 | 
			
		||||
    // To remain consistent with our old web tests, we want to move some downloads
 | 
			
		||||
    // (images) to another directory.
 | 
			
		||||
    await moveDownloadedFileTo(page, downloadLocation)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
@ -818,12 +832,13 @@ export async function setup(
 | 
			
		||||
  testInfo?: TestInfo
 | 
			
		||||
) {
 | 
			
		||||
  await context.addInitScript(
 | 
			
		||||
    async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => {
 | 
			
		||||
    async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY, PLAYWRIGHT_TEST_DIR }) => {
 | 
			
		||||
      localStorage.clear()
 | 
			
		||||
      localStorage.setItem('TOKEN_PERSIST_KEY', token)
 | 
			
		||||
      localStorage.setItem('persistCode', ``)
 | 
			
		||||
      localStorage.setItem(settingsKey, settings)
 | 
			
		||||
      localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
 | 
			
		||||
      localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      token: secrets.token,
 | 
			
		||||
@ -840,6 +855,7 @@ export async function setup(
 | 
			
		||||
        } as Partial<SaveSettingsPayload>,
 | 
			
		||||
      }),
 | 
			
		||||
      IS_PLAYWRIGHT_KEY,
 | 
			
		||||
      PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory,
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
@ -901,11 +917,13 @@ export async function setupElectron({
 | 
			
		||||
  const context = electronApp.context()
 | 
			
		||||
  const page = await electronApp.firstWindow()
 | 
			
		||||
 | 
			
		||||
  page.TEST_SETTINGS_FILE_KEY = projectDirName
 | 
			
		||||
 | 
			
		||||
  context.on('console', console.log)
 | 
			
		||||
  page.on('console', console.log)
 | 
			
		||||
 | 
			
		||||
  if (cleanProjectDir) {
 | 
			
		||||
    const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
 | 
			
		||||
    const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
 | 
			
		||||
    const settingsOverrides = TOML.stringify(
 | 
			
		||||
      appSettings
 | 
			
		||||
        ? {
 | 
			
		||||
@ -1022,7 +1040,7 @@ export async function createProject({
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function executorInputPath(fileName: string): string {
 | 
			
		||||
  return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
 | 
			
		||||
  return path.join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function doAndWaitForImageDiff(
 | 
			
		||||
 | 
			
		||||
@ -1,55 +1,45 @@
 | 
			
		||||
import { test, expect } from '@playwright/test'
 | 
			
		||||
import { test, expect } from './zoo-test'
 | 
			
		||||
 | 
			
		||||
import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils'
 | 
			
		||||
import { doExport, getUtils, makeTemplate } from './test-utils'
 | 
			
		||||
 | 
			
		||||
test.beforeEach(async ({ context, page }, testInfo) => {
 | 
			
		||||
  await setup(context, page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.afterEach(async ({ page }, testInfo) => {
 | 
			
		||||
  await tearDown(page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Units menu', async ({ page }) => {
 | 
			
		||||
test.fixme('Units menu', async ({ page, homePage }) => {
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
await homePage.goToModelingScene()
 | 
			
		||||
 | 
			
		||||
  const unitsMenuButton = page.getByRole('button', {
 | 
			
		||||
    name: 'Current Units',
 | 
			
		||||
    exact: false,
 | 
			
		||||
  })
 | 
			
		||||
  await expect(unitsMenuButton).toBeVisible()
 | 
			
		||||
  await expect(unitsMenuButton).toContainText('in')
 | 
			
		||||
 | 
			
		||||
  await unitsMenuButton.click()
 | 
			
		||||
  const millimetersButton = page.getByRole('button', { name: 'Millimeters' })
 | 
			
		||||
 | 
			
		||||
  await expect(millimetersButton).toBeVisible()
 | 
			
		||||
  await millimetersButton.click()
 | 
			
		||||
 | 
			
		||||
  // Look out for the toast message
 | 
			
		||||
  const toastMessage = page.getByText(
 | 
			
		||||
    `Set default unit to "mm" for this project`
 | 
			
		||||
  )
 | 
			
		||||
  await expect(toastMessage).toBeVisible()
 | 
			
		||||
 | 
			
		||||
  // Verify that the popover has closed
 | 
			
		||||
  await expect(millimetersButton).not.toBeAttached()
 | 
			
		||||
 | 
			
		||||
  // Verify that the button label has updated
 | 
			
		||||
  await expect(unitsMenuButton).toContainText('mm')
 | 
			
		||||
const unitsMenuButton = page.getByRole('button', {
 | 
			
		||||
  name: 'Current Units',
 | 
			
		||||
  exact: false,
 | 
			
		||||
})
 | 
			
		||||
await expect(unitsMenuButton).toBeVisible()
 | 
			
		||||
await expect(unitsMenuButton).toContainText('in')
 | 
			
		||||
 | 
			
		||||
test('Successful export shows a success toast', async ({ page }) => {
 | 
			
		||||
  // FYI this test doesn't work with only engine running locally
 | 
			
		||||
  // And you will need to have the KittyCAD CLI installed
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.addInitScript(async () => {
 | 
			
		||||
    ;(window as any).playwrightSkipFilePicker = true
 | 
			
		||||
    localStorage.setItem(
 | 
			
		||||
      'persistCode',
 | 
			
		||||
      `topAng = 25
 | 
			
		||||
await unitsMenuButton.click()
 | 
			
		||||
const millimetersButton = page.getByRole('button', { name: 'Millimeters' })
 | 
			
		||||
 | 
			
		||||
await expect(millimetersButton).toBeVisible()
 | 
			
		||||
await millimetersButton.click()
 | 
			
		||||
 | 
			
		||||
// Look out for the toast message
 | 
			
		||||
const toastMessage = page.getByText(
 | 
			
		||||
  `Set default unit to "mm" for this project`
 | 
			
		||||
)
 | 
			
		||||
await expect(toastMessage).toBeVisible()
 | 
			
		||||
 | 
			
		||||
// Verify that the popover has closed
 | 
			
		||||
await expect(millimetersButton).not.toBeAttached()
 | 
			
		||||
 | 
			
		||||
// Verify that the button label has updated
 | 
			
		||||
await expect(unitsMenuButton).toContainText('mm') })
 | 
			
		||||
 | 
			
		||||
test('Successful export shows a success toast', async ({ page, homePage }) => { // FYI this test doesn't work with only engine running locally
 | 
			
		||||
// And you will need to have the KittyCAD CLI installed
 | 
			
		||||
const u = await getUtils(page)
 | 
			
		||||
await page.addInitScript(async () => {
 | 
			
		||||
  ;(window as any).playwrightSkipFilePicker = true
 | 
			
		||||
  localStorage.setItem(
 | 
			
		||||
    'persistCode',
 | 
			
		||||
    `topAng = 25
 | 
			
		||||
bottomAng = 35
 | 
			
		||||
baseLen = 3.5
 | 
			
		||||
baseHeight = 1
 | 
			
		||||
@ -57,204 +47,188 @@ totalHeightHalf = 2
 | 
			
		||||
armThick = 0.5
 | 
			
		||||
totalLen = 9.5
 | 
			
		||||
part001 = startSketchOn('-XZ')
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
  |> yLine(baseHeight, %)
 | 
			
		||||
  |> xLine(baseLen, %)
 | 
			
		||||
  |> angledLineToY({
 | 
			
		||||
        angle: topAng,
 | 
			
		||||
        to: totalHeightHalf,
 | 
			
		||||
      }, %, $seg04)
 | 
			
		||||
  |> xLineTo(totalLen, %, $seg03)
 | 
			
		||||
  |> yLine(-armThick, %, $seg01)
 | 
			
		||||
  |> angledLineThatIntersects({
 | 
			
		||||
        angle: HALF_TURN,
 | 
			
		||||
        offset: -armThick,
 | 
			
		||||
        intersectTag: seg04
 | 
			
		||||
      }, %)
 | 
			
		||||
  |> angledLineToY([segAng(seg04) + 180, ZERO], %)
 | 
			
		||||
  |> angledLineToY({
 | 
			
		||||
        angle: -bottomAng,
 | 
			
		||||
        to: -totalHeightHalf - armThick,
 | 
			
		||||
      }, %, $seg02)
 | 
			
		||||
  |> xLineTo(segEndX(seg03) + 0, %)
 | 
			
		||||
  |> yLine(-segLen(seg01), %)
 | 
			
		||||
  |> angledLineThatIntersects({
 | 
			
		||||
        angle: HALF_TURN,
 | 
			
		||||
        offset: -armThick,
 | 
			
		||||
        intersectTag: seg02
 | 
			
		||||
      }, %)
 | 
			
		||||
  |> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
 | 
			
		||||
  |> xLineTo(ZERO, %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  |> extrude(4, %)`
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
  await u.openDebugPanel()
 | 
			
		||||
  await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
  await u.waitForCmdReceive('extrude')
 | 
			
		||||
  await page.waitForTimeout(1000)
 | 
			
		||||
  await u.clearAndCloseDebugPanel()
 | 
			
		||||
 | 
			
		||||
  await doExport(
 | 
			
		||||
    {
 | 
			
		||||
      type: 'gltf',
 | 
			
		||||
      storage: 'embedded',
 | 
			
		||||
      presentation: 'pretty',
 | 
			
		||||
    },
 | 
			
		||||
    page
 | 
			
		||||
|> startProfileAt([0, 0], %)
 | 
			
		||||
|> yLine(baseHeight, %)
 | 
			
		||||
|> xLine(baseLen, %)
 | 
			
		||||
|> angledLineToY({
 | 
			
		||||
      angle: topAng,
 | 
			
		||||
      to: totalHeightHalf,
 | 
			
		||||
    }, %, $seg04)
 | 
			
		||||
|> xLineTo(totalLen, %, $seg03)
 | 
			
		||||
|> yLine(-armThick, %, $seg01)
 | 
			
		||||
|> angledLineThatIntersects({
 | 
			
		||||
      angle: HALF_TURN,
 | 
			
		||||
      offset: -armThick,
 | 
			
		||||
      intersectTag: seg04
 | 
			
		||||
    }, %)
 | 
			
		||||
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
 | 
			
		||||
|> angledLineToY({
 | 
			
		||||
      angle: -bottomAng,
 | 
			
		||||
      to: -totalHeightHalf - armThick,
 | 
			
		||||
    }, %, $seg02)
 | 
			
		||||
|> xLineTo(segEndX(seg03) + 0, %)
 | 
			
		||||
|> yLine(-segLen(seg01), %)
 | 
			
		||||
|> angledLineThatIntersects({
 | 
			
		||||
      angle: HALF_TURN,
 | 
			
		||||
      offset: -armThick,
 | 
			
		||||
      intersectTag: seg02
 | 
			
		||||
    }, %)
 | 
			
		||||
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
 | 
			
		||||
|> xLineTo(ZERO, %)
 | 
			
		||||
|> close(%)
 | 
			
		||||
|> extrude(4, %)`
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
await homePage.goToModelingScene()
 | 
			
		||||
await u.openDebugPanel()
 | 
			
		||||
await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
await u.waitForCmdReceive('extrude')
 | 
			
		||||
await page.waitForTimeout(1000)
 | 
			
		||||
await u.clearAndCloseDebugPanel()
 | 
			
		||||
 | 
			
		||||
await doExport(
 | 
			
		||||
  {
 | 
			
		||||
    type: 'gltf',
 | 
			
		||||
    storage: 'embedded',
 | 
			
		||||
    presentation: 'pretty',
 | 
			
		||||
  },
 | 
			
		||||
  page
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
  // This is the main thing we're testing,
 | 
			
		||||
  // We test the export functionality across all
 | 
			
		||||
  // file types in snapshot-tests.spec.ts
 | 
			
		||||
  await expect(page.getByText('Exported successfully')).toBeVisible()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Paste should not work unless an input is focused', async ({
 | 
			
		||||
  page,
 | 
			
		||||
  browserName,
 | 
			
		||||
}) => {
 | 
			
		||||
  // To run this test locally, uncomment Firefox in playwright.config.ts
 | 
			
		||||
  test.skip(
 | 
			
		||||
    browserName !== 'firefox',
 | 
			
		||||
    "This bug is really Firefox-only, which we don't run in CI."
 | 
			
		||||
test('Paste should not work unless an input is focused', async ({ page, browserName, homePage }) => { // To run this test locally, uncomment Firefox in playwright.config.ts
 | 
			
		||||
test.skip(
 | 
			
		||||
  browserName !== 'firefox',
 | 
			
		||||
  "This bug is really Firefox-only, which we don't run in CI."
 | 
			
		||||
)
 | 
			
		||||
const u = await getUtils(page)
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
await homePage.goToModelingScene()
 | 
			
		||||
await page
 | 
			
		||||
  .getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
  .waitFor({ state: 'visible' })
 | 
			
		||||
 | 
			
		||||
const codeEditorText = page.locator('.cm-content')
 | 
			
		||||
const pasteContent = `// was this pasted?`
 | 
			
		||||
const typeContent = `// this should be typed`
 | 
			
		||||
 | 
			
		||||
// Load text into the clipboard
 | 
			
		||||
await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent)
 | 
			
		||||
 | 
			
		||||
// Focus the text editor
 | 
			
		||||
await codeEditorText.focus()
 | 
			
		||||
 | 
			
		||||
// Show that we can type into it
 | 
			
		||||
await page.keyboard.type(typeContent)
 | 
			
		||||
await page.keyboard.press('Enter')
 | 
			
		||||
 | 
			
		||||
// Paste without the code pane focused
 | 
			
		||||
await codeEditorText.blur()
 | 
			
		||||
await page.keyboard.press('ControlOrMeta+KeyV')
 | 
			
		||||
 | 
			
		||||
// Show that the paste didn't work but typing did
 | 
			
		||||
await expect(codeEditorText).not.toContainText(pasteContent)
 | 
			
		||||
await expect(codeEditorText).toContainText(typeContent)
 | 
			
		||||
 | 
			
		||||
// Paste with the code editor focused
 | 
			
		||||
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
 | 
			
		||||
await codeEditorText.focus()
 | 
			
		||||
await page.keyboard.press('ControlOrMeta+KeyV')
 | 
			
		||||
await expect(
 | 
			
		||||
  await page.evaluate(
 | 
			
		||||
    () => document.querySelector('.cm-content')?.textContent
 | 
			
		||||
  )
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
  await page
 | 
			
		||||
    .getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
    .waitFor({ state: 'visible' })
 | 
			
		||||
).toContain(pasteContent) })
 | 
			
		||||
 | 
			
		||||
  const codeEditorText = page.locator('.cm-content')
 | 
			
		||||
  const pasteContent = `// was this pasted?`
 | 
			
		||||
  const typeContent = `// this should be typed`
 | 
			
		||||
test('Keyboard shortcuts can be viewed through the help menu', async ({ page,  homePage }) => { const u = await getUtils(page)
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
await homePage.goToModelingScene()
 | 
			
		||||
 | 
			
		||||
  // Load text into the clipboard
 | 
			
		||||
  await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent)
 | 
			
		||||
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
 | 
			
		||||
await page
 | 
			
		||||
  .getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
  .waitFor({ state: 'visible' })
 | 
			
		||||
 | 
			
		||||
  // Focus the text editor
 | 
			
		||||
  await codeEditorText.focus()
 | 
			
		||||
// Open the help menu
 | 
			
		||||
await page.getByRole('button', { name: 'Help and resources' }).click()
 | 
			
		||||
 | 
			
		||||
  // Show that we can type into it
 | 
			
		||||
  await page.keyboard.type(typeContent)
 | 
			
		||||
  await page.keyboard.press('Enter')
 | 
			
		||||
// Open the keyboard shortcuts
 | 
			
		||||
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
 | 
			
		||||
 | 
			
		||||
  // Paste without the code pane focused
 | 
			
		||||
  await codeEditorText.blur()
 | 
			
		||||
  await page.keyboard.press('ControlOrMeta+KeyV')
 | 
			
		||||
 | 
			
		||||
  // Show that the paste didn't work but typing did
 | 
			
		||||
  await expect(codeEditorText).not.toContainText(pasteContent)
 | 
			
		||||
  await expect(codeEditorText).toContainText(typeContent)
 | 
			
		||||
 | 
			
		||||
  // Paste with the code editor focused
 | 
			
		||||
  // Following this guidance: https://github.com/microsoft/playwright/issues/8114
 | 
			
		||||
  await codeEditorText.focus()
 | 
			
		||||
  await page.keyboard.press('ControlOrMeta+KeyV')
 | 
			
		||||
  await expect(
 | 
			
		||||
    await page.evaluate(
 | 
			
		||||
      () => document.querySelector('.cm-content')?.textContent
 | 
			
		||||
    )
 | 
			
		||||
  ).toContain(pasteContent)
 | 
			
		||||
// Verify the URL and that you can see a list of shortcuts
 | 
			
		||||
await expect.poll(() => page.url()).toContain('?tab=keybindings')
 | 
			
		||||
await expect(
 | 
			
		||||
  page.getByRole('heading', { name: 'Enter Sketch Mode' })
 | 
			
		||||
).toBeAttached()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Keyboard shortcuts can be viewed through the help menu', async ({
 | 
			
		||||
  page,
 | 
			
		||||
}) => {
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
test('First escape in tool pops you out of tool, second exits sketch mode', async ({ page,  homePage }) => { // Wait for the app to be ready for use
 | 
			
		||||
const u = await getUtils(page)
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
  await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
 | 
			
		||||
  await page
 | 
			
		||||
    .getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
    .waitFor({ state: 'visible' })
 | 
			
		||||
await homePage.goToModelingScene()
 | 
			
		||||
await u.openDebugPanel()
 | 
			
		||||
await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
await u.closeDebugPanel()
 | 
			
		||||
 | 
			
		||||
  // Open the help menu
 | 
			
		||||
  await page.getByRole('button', { name: 'Help and resources' }).click()
 | 
			
		||||
 | 
			
		||||
  // Open the keyboard shortcuts
 | 
			
		||||
  await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
 | 
			
		||||
 | 
			
		||||
  // Verify the URL and that you can see a list of shortcuts
 | 
			
		||||
  await expect(page.url()).toContain('?tab=keybindings')
 | 
			
		||||
  await expect(
 | 
			
		||||
    page.getByRole('heading', { name: 'Enter Sketch Mode' })
 | 
			
		||||
  ).toBeAttached()
 | 
			
		||||
const lineButton = page.getByRole('button', {
 | 
			
		||||
  name: 'line Line',
 | 
			
		||||
  exact: true,
 | 
			
		||||
})
 | 
			
		||||
const arcButton = page.getByRole('button', {
 | 
			
		||||
  name: 'arc Tangential Arc',
 | 
			
		||||
  exact: true,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('First escape in tool pops you out of tool, second exits sketch mode', async ({
 | 
			
		||||
  page,
 | 
			
		||||
}) => {
 | 
			
		||||
  // Wait for the app to be ready for use
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
// Test these hotkeys perform actions when
 | 
			
		||||
// focus is on the canvas
 | 
			
		||||
await page.mouse.move(600, 250)
 | 
			
		||||
await page.mouse.click(600, 250)
 | 
			
		||||
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
  await u.openDebugPanel()
 | 
			
		||||
  await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
  await u.closeDebugPanel()
 | 
			
		||||
// Start a sketch
 | 
			
		||||
await page.keyboard.press('s')
 | 
			
		||||
await page.mouse.move(800, 300)
 | 
			
		||||
await page.mouse.click(800, 300)
 | 
			
		||||
await page.waitForTimeout(1000)
 | 
			
		||||
await expect(lineButton).toBeVisible()
 | 
			
		||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
 | 
			
		||||
  const lineButton = page.getByRole('button', {
 | 
			
		||||
    name: 'line Line',
 | 
			
		||||
    exact: true,
 | 
			
		||||
  })
 | 
			
		||||
  const arcButton = page.getByRole('button', {
 | 
			
		||||
    name: 'arc Tangential Arc',
 | 
			
		||||
    exact: true,
 | 
			
		||||
  })
 | 
			
		||||
// Draw a line
 | 
			
		||||
await page.mouse.move(700, 200, { steps: 5 })
 | 
			
		||||
await page.mouse.click(700, 200)
 | 
			
		||||
await page.mouse.move(800, 250, { steps: 5 })
 | 
			
		||||
await page.mouse.click(800, 250)
 | 
			
		||||
// Unequip line tool
 | 
			
		||||
await page.keyboard.press('Escape')
 | 
			
		||||
// Make sure we didn't pop out of sketch mode.
 | 
			
		||||
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
 | 
			
		||||
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
// Equip arc tool
 | 
			
		||||
await page.keyboard.press('a')
 | 
			
		||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
await page.mouse.move(1000, 100, { steps: 5 })
 | 
			
		||||
await page.mouse.click(1000, 100)
 | 
			
		||||
await page.keyboard.press('Escape')
 | 
			
		||||
await page.keyboard.press('l')
 | 
			
		||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
 | 
			
		||||
  // Test these hotkeys perform actions when
 | 
			
		||||
  // focus is on the canvas
 | 
			
		||||
  await page.mouse.move(600, 250)
 | 
			
		||||
  await page.mouse.click(600, 250)
 | 
			
		||||
// Do not close the sketch.
 | 
			
		||||
// On close it will exit sketch mode.
 | 
			
		||||
 | 
			
		||||
  // Start a sketch
 | 
			
		||||
  await page.keyboard.press('s')
 | 
			
		||||
  await page.mouse.move(800, 300)
 | 
			
		||||
  await page.mouse.click(800, 300)
 | 
			
		||||
  await page.waitForTimeout(1000)
 | 
			
		||||
  await expect(lineButton).toBeVisible()
 | 
			
		||||
  await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
 | 
			
		||||
  // Draw a line
 | 
			
		||||
  await page.mouse.move(700, 200, { steps: 5 })
 | 
			
		||||
  await page.mouse.click(700, 200)
 | 
			
		||||
  await page.mouse.move(800, 250, { steps: 5 })
 | 
			
		||||
  await page.mouse.click(800, 250)
 | 
			
		||||
  // Unequip line tool
 | 
			
		||||
  await page.keyboard.press('Escape')
 | 
			
		||||
  // Make sure we didn't pop out of sketch mode.
 | 
			
		||||
  await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
 | 
			
		||||
  await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
  // Equip arc tool
 | 
			
		||||
  await page.keyboard.press('a')
 | 
			
		||||
  await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
  await page.mouse.move(1000, 100, { steps: 5 })
 | 
			
		||||
  await page.mouse.click(1000, 100)
 | 
			
		||||
  await page.keyboard.press('Escape')
 | 
			
		||||
  await page.keyboard.press('l')
 | 
			
		||||
  await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
 | 
			
		||||
 | 
			
		||||
  // Do not close the sketch.
 | 
			
		||||
  // On close it will exit sketch mode.
 | 
			
		||||
 | 
			
		||||
  // Unequip line tool
 | 
			
		||||
  await page.keyboard.press('Escape')
 | 
			
		||||
  await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
 | 
			
		||||
  await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
 | 
			
		||||
  // Make sure we didn't pop out of sketch mode.
 | 
			
		||||
  await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
 | 
			
		||||
  // Exit sketch
 | 
			
		||||
  await page.keyboard.press('Escape')
 | 
			
		||||
  await expect(
 | 
			
		||||
    page.getByRole('button', { name: 'Exit Sketch' })
 | 
			
		||||
  ).not.toBeVisible()
 | 
			
		||||
})
 | 
			
		||||
// Unequip line tool
 | 
			
		||||
await page.keyboard.press('Escape')
 | 
			
		||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
 | 
			
		||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
 | 
			
		||||
// Make sure we didn't pop out of sketch mode.
 | 
			
		||||
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
 | 
			
		||||
// Exit sketch
 | 
			
		||||
await page.keyboard.press('Escape')
 | 
			
		||||
await expect(
 | 
			
		||||
  page.getByRole('button', { name: 'Exit Sketch' })
 | 
			
		||||
).not.toBeVisible() })
 | 
			
		||||
 | 
			
		||||
test.fixme(
 | 
			
		||||
  'Basic default modeling and sketch hotkeys work',
 | 
			
		||||
@ -285,8 +259,8 @@ test.fixme(
 | 
			
		||||
          })
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
      await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
      await u.waitForAuthSkipAppStart()
 | 
			
		||||
      await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
      await homePage.goToModelingScene()
 | 
			
		||||
      await u.openDebugPanel()
 | 
			
		||||
      await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
      await u.closeDebugPanel()
 | 
			
		||||
@ -437,171 +411,171 @@ test.fixme(
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
test('Delete key does not navigate back', async ({ page }) => {
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
  await page.goto('/')
 | 
			
		||||
  await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
 | 
			
		||||
test('Delete key does not navigate back', async ({ page, homePage }) => {
 | 
			
		||||
 | 
			
		||||
  const settingsButton = page.getByRole('link', {
 | 
			
		||||
    name: 'Settings',
 | 
			
		||||
    exact: false,
 | 
			
		||||
  })
 | 
			
		||||
  const settingsCloseButton = page.getByTestId('settings-close-button')
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
await homePage.goToModelingScene();
 | 
			
		||||
 | 
			
		||||
  await settingsButton.click()
 | 
			
		||||
  await expect(page.url()).toContain('/settings')
 | 
			
		||||
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
 | 
			
		||||
 | 
			
		||||
  // Make sure that delete doesn't go back from settings
 | 
			
		||||
  await page.keyboard.press('Delete')
 | 
			
		||||
  await expect(page.url()).toContain('/settings')
 | 
			
		||||
const settingsButton = page.getByRole('link', {
 | 
			
		||||
  name: 'Settings',
 | 
			
		||||
  exact: false,
 | 
			
		||||
})
 | 
			
		||||
const settingsCloseButton = page.getByTestId('settings-close-button')
 | 
			
		||||
 | 
			
		||||
  // Now close the settings and try delete again,
 | 
			
		||||
  // make sure it doesn't go back to settings
 | 
			
		||||
  await settingsCloseButton.click()
 | 
			
		||||
  await page.keyboard.press('Delete')
 | 
			
		||||
  await expect(page.url()).not.toContain('/settings')
 | 
			
		||||
await settingsButton.click()
 | 
			
		||||
await expect.poll(() => page.url()).toContain('/settings')
 | 
			
		||||
 | 
			
		||||
// Make sure that delete doesn't go back from settings
 | 
			
		||||
await page.keyboard.press('Delete')
 | 
			
		||||
await expect.poll(() => page.url()).toContain('/settings')
 | 
			
		||||
 | 
			
		||||
// Now close the settings and try delete again,
 | 
			
		||||
// make sure it doesn't go back to settings
 | 
			
		||||
await settingsCloseButton.click()
 | 
			
		||||
await page.keyboard.press('Delete')
 | 
			
		||||
await expect.poll(() => page.url()).not.toContain('/settings') })
 | 
			
		||||
 | 
			
		||||
test('Sketch on face', async ({ page, homePage }) => { test.setTimeout(90_000)
 | 
			
		||||
const u = await getUtils(page)
 | 
			
		||||
await page.addInitScript(async () => {
 | 
			
		||||
  localStorage.setItem(
 | 
			
		||||
    'persistCode',
 | 
			
		||||
    `sketch001 = startSketchOn('XZ')
 | 
			
		||||
|> startProfileAt([3.29, 7.86], %)
 | 
			
		||||
|> line([2.48, 2.44], %)
 | 
			
		||||
|> line([2.66, 1.17], %)
 | 
			
		||||
|> line([3.75, 0.46], %)
 | 
			
		||||
|> line([4.99, -0.46], %)
 | 
			
		||||
|> line([3.3, -2.12], %)
 | 
			
		||||
|> line([2.16, -3.33], %)
 | 
			
		||||
|> line([0.85, -3.08], %)
 | 
			
		||||
|> line([-0.18, -3.36], %)
 | 
			
		||||
|> line([-3.86, -2.73], %)
 | 
			
		||||
|> line([-17.67, 0.85], %)
 | 
			
		||||
|> close(%)
 | 
			
		||||
extrude001 = extrude(5 + 7, sketch001)`
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Sketch on face', async ({ page }) => {
 | 
			
		||||
  test.setTimeout(90_000)
 | 
			
		||||
  const u = await getUtils(page)
 | 
			
		||||
  await page.addInitScript(async () => {
 | 
			
		||||
    localStorage.setItem(
 | 
			
		||||
      'persistCode',
 | 
			
		||||
      `sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([3.29, 7.86], %)
 | 
			
		||||
  |> line([2.48, 2.44], %)
 | 
			
		||||
  |> line([2.66, 1.17], %)
 | 
			
		||||
  |> line([3.75, 0.46], %)
 | 
			
		||||
  |> line([4.99, -0.46], %)
 | 
			
		||||
  |> line([3.3, -2.12], %)
 | 
			
		||||
  |> line([2.16, -3.33], %)
 | 
			
		||||
  |> line([0.85, -3.08], %)
 | 
			
		||||
  |> line([-0.18, -3.36], %)
 | 
			
		||||
  |> line([-3.86, -2.73], %)
 | 
			
		||||
  |> line([-17.67, 0.85], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
  extrude001 = extrude(5 + 7, sketch001)`
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
await homePage.goToModelingScene()
 | 
			
		||||
 | 
			
		||||
  await u.waitForAuthSkipAppStart()
 | 
			
		||||
// wait for execution done
 | 
			
		||||
await u.openDebugPanel()
 | 
			
		||||
await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
await u.closeDebugPanel()
 | 
			
		||||
 | 
			
		||||
  // wait for execution done
 | 
			
		||||
  await u.openDebugPanel()
 | 
			
		||||
  await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
  await u.closeDebugPanel()
 | 
			
		||||
await expect(
 | 
			
		||||
  page.getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
).not.toBeDisabled()
 | 
			
		||||
 | 
			
		||||
  await expect(
 | 
			
		||||
    page.getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
  ).not.toBeDisabled()
 | 
			
		||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
			
		||||
await page.waitForTimeout(300)
 | 
			
		||||
 | 
			
		||||
  await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
			
		||||
  await page.waitForTimeout(300)
 | 
			
		||||
let previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  let previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
await u.openAndClearDebugPanel()
 | 
			
		||||
await u.doAndWaitForCmd(
 | 
			
		||||
  () => page.mouse.click(625, 165),
 | 
			
		||||
  'default_camera_get_settings',
 | 
			
		||||
  true
 | 
			
		||||
)
 | 
			
		||||
await page.waitForTimeout(150)
 | 
			
		||||
await u.closeDebugPanel()
 | 
			
		||||
 | 
			
		||||
  await u.openAndClearDebugPanel()
 | 
			
		||||
  await u.doAndWaitForCmd(
 | 
			
		||||
    () => page.mouse.click(625, 165),
 | 
			
		||||
    'default_camera_get_settings',
 | 
			
		||||
    true
 | 
			
		||||
  )
 | 
			
		||||
  await page.waitForTimeout(150)
 | 
			
		||||
  await u.closeDebugPanel()
 | 
			
		||||
const firstClickPosition = [612, 238]
 | 
			
		||||
const secondClickPosition = [661, 242]
 | 
			
		||||
const thirdClickPosition = [609, 267]
 | 
			
		||||
 | 
			
		||||
  const firstClickPosition = [612, 238]
 | 
			
		||||
  const secondClickPosition = [661, 242]
 | 
			
		||||
  const thirdClickPosition = [609, 267]
 | 
			
		||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
 | 
			
		||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
 | 
			
		||||
  await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
  previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
 | 
			
		||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
  await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
 | 
			
		||||
  await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
  previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
 | 
			
		||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
  await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
 | 
			
		||||
  await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
  previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
 | 
			
		||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
  await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
 | 
			
		||||
  await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
  previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  await expect.poll(u.normalisedEditorCode).toContain(
 | 
			
		||||
    u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
 | 
			
		||||
await expect.poll(u.normalisedEditorCode).toContain(
 | 
			
		||||
  u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
 | 
			
		||||
  |> startProfileAt([-12.94, 6.6], %)
 | 
			
		||||
  |> line([2.45, -0.2], %)
 | 
			
		||||
  |> line([-2.6, -1.25], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)`)
 | 
			
		||||
  )
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
  await u.openAndClearDebugPanel()
 | 
			
		||||
  await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
  await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
await u.openAndClearDebugPanel()
 | 
			
		||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
 | 
			
		||||
  await u.updateCamPosition([1049, 239, 686])
 | 
			
		||||
  await u.closeDebugPanel()
 | 
			
		||||
await u.updateCamPosition([1049, 239, 686])
 | 
			
		||||
await u.closeDebugPanel()
 | 
			
		||||
 | 
			
		||||
  await page.getByText('startProfileAt([-12').click()
 | 
			
		||||
  await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
 | 
			
		||||
  await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
			
		||||
  await page.waitForTimeout(400)
 | 
			
		||||
  await page.waitForTimeout(150)
 | 
			
		||||
  await page.setViewportSize({ width: 1200, height: 1200 })
 | 
			
		||||
  await u.openAndClearDebugPanel()
 | 
			
		||||
  await u.updateCamPosition([452, -152, 1166])
 | 
			
		||||
  await u.closeDebugPanel()
 | 
			
		||||
  await page.waitForTimeout(200)
 | 
			
		||||
await page.getByText('startProfileAt([-12').click()
 | 
			
		||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
 | 
			
		||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
 | 
			
		||||
await page.waitForTimeout(400)
 | 
			
		||||
await page.waitForTimeout(150)
 | 
			
		||||
await page.setBodyDimensions({ width: 1200, height: 1200 })
 | 
			
		||||
await u.openAndClearDebugPanel()
 | 
			
		||||
await u.updateCamPosition([452, -152, 1166])
 | 
			
		||||
await u.closeDebugPanel()
 | 
			
		||||
await page.waitForTimeout(200)
 | 
			
		||||
 | 
			
		||||
  const pointToDragFirst = [787, 565]
 | 
			
		||||
  await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
 | 
			
		||||
  await page.mouse.down()
 | 
			
		||||
  await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
 | 
			
		||||
    steps: 5,
 | 
			
		||||
  })
 | 
			
		||||
  await page.mouse.up()
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
  await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
  previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
  const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01)
 | 
			
		||||
  |> startProfileAt([-12.83, 6.7], %)
 | 
			
		||||
  |> line([${[2.28, 2.35]}, -${0.07}], %)
 | 
			
		||||
  |> line([-3.05, -1.47], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)`
 | 
			
		||||
 | 
			
		||||
  await expect(page.locator('.cm-content')).toHaveText(result.regExp)
 | 
			
		||||
 | 
			
		||||
  // exit sketch
 | 
			
		||||
  await u.openAndClearDebugPanel()
 | 
			
		||||
  await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
  await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
 | 
			
		||||
  await page.getByText('startProfileAt([-12').click()
 | 
			
		||||
 | 
			
		||||
  await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
  await page.getByRole('button', { name: 'Extrude' }).click()
 | 
			
		||||
 | 
			
		||||
  await expect(page.getByTestId('command-bar')).toBeVisible()
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
 | 
			
		||||
  await page.getByRole('button', { name: 'arrow right Continue' }).click()
 | 
			
		||||
  await page.waitForTimeout(100)
 | 
			
		||||
  await expect(page.getByText('Confirm Extrude')).toBeVisible()
 | 
			
		||||
  await page.getByRole('button', { name: 'checkmark Submit command' }).click()
 | 
			
		||||
 | 
			
		||||
  const result2 = result.genNext`
 | 
			
		||||
  const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
 | 
			
		||||
  await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
 | 
			
		||||
const pointToDragFirst = [787, 565]
 | 
			
		||||
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
 | 
			
		||||
await page.mouse.down()
 | 
			
		||||
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
 | 
			
		||||
  steps: 5,
 | 
			
		||||
})
 | 
			
		||||
await page.mouse.up()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
 | 
			
		||||
previousCodeContent = await page.locator('.cm-content').innerText()
 | 
			
		||||
 | 
			
		||||
const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01)
 | 
			
		||||
|> startProfileAt([-12.83, 6.7], %)
 | 
			
		||||
|> line([${[2.28, 2.35]}, -${0.07}], %)
 | 
			
		||||
|> line([-3.05, -1.47], %)
 | 
			
		||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
|> close(%)`
 | 
			
		||||
 | 
			
		||||
await expect(page.locator('.cm-content')).toHaveText(result.regExp)
 | 
			
		||||
 | 
			
		||||
// exit sketch
 | 
			
		||||
await u.openAndClearDebugPanel()
 | 
			
		||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
 | 
			
		||||
await page.getByText('startProfileAt([-12').click()
 | 
			
		||||
 | 
			
		||||
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
await page.getByRole('button', { name: 'Extrude' }).click()
 | 
			
		||||
 | 
			
		||||
await expect(page.getByTestId('command-bar')).toBeVisible()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
 | 
			
		||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
 | 
			
		||||
await page.waitForTimeout(100)
 | 
			
		||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
 | 
			
		||||
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
 | 
			
		||||
 | 
			
		||||
const result2 = result.genNext`
 | 
			
		||||
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
 | 
			
		||||
await expect(page.locator('.cm-content')).toHaveText(result2.regExp) })
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,10 @@ export { expect, Page, BrowserContext, TestInfo } from '@playwright/test'
 | 
			
		||||
// switch between web and electron if needed.
 | 
			
		||||
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// In JavaScript you cannot replace a function's body only (despite functions
 | 
			
		||||
// are themselves objects, which you'd expect a body property or something...)
 | 
			
		||||
// So we must redefine the function and then re-attach properties.
 | 
			
		||||
export function test(desc, objOrFn, fnMaybe) {
 | 
			
		||||
  const hasTestConf = typeof objOrFn === 'object'
 | 
			
		||||
  const fn = hasTestConf ? fnMaybe : objOrFn
 | 
			
		||||
@ -54,6 +58,13 @@ export function test(desc, objOrFn, fnMaybe) {
 | 
			
		||||
        }, dims)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // We need to expose this in order for some tests that require folder
 | 
			
		||||
      // creation. Before they used to do this by their own electronSetup({...})
 | 
			
		||||
      // calls.
 | 
			
		||||
      tronApp.context.folderSetupFn = function(fn) {
 | 
			
		||||
        return fn(tronApp.dir).then(() => ({ dir: tronApp.dir }))
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await fn(
 | 
			
		||||
        {
 | 
			
		||||
          context: tronApp.context,
 | 
			
		||||
@ -74,3 +85,4 @@ test.afterEach = pwTestFnWithFixtures.afterEach
 | 
			
		||||
test.step = pwTestFnWithFixtures.step
 | 
			
		||||
test.skip = pwTestFnWithFixtures.skip
 | 
			
		||||
test.setTimeout = pwTestFnWithFixtures.setTimeout
 | 
			
		||||
test.fixme = pwTestFnWithFixtures.fixme
 | 
			
		||||
 | 
			
		||||
@ -17,9 +17,14 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (window.electron.process.env.IS_PLAYWRIGHT) {
 | 
			
		||||
        // skip file picker, save to default location
 | 
			
		||||
        // Skip file picker, save to the test dir downloads directory
 | 
			
		||||
        const downloadDir = window.electron.join(
 | 
			
		||||
          window.electron.process.env.TEST_SETTINGS_FILE_KEY,
 | 
			
		||||
          "downloads-during-playwright",
 | 
			
		||||
        )
 | 
			
		||||
        await window.electron.mkdir(downloadDir, { recursive: true })
 | 
			
		||||
        await window.electron.writeFile(
 | 
			
		||||
          file.name,
 | 
			
		||||
          window.electron.join(downloadDir, file.name),
 | 
			
		||||
          new Uint8Array(file.contents)
 | 
			
		||||
        )
 | 
			
		||||
        toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user