Assemblies: Load outside files into project via point-and-click (#6217)
* WIP: Add point-and-click Import for geometry Will eventually fix #6120 Right now the whole loop is there but the codemod doesn't work yet * Better pathToNOde, log on non-working cm dispatch call * Add workaround to updateModelingState not working * Back to updateModelingState with a skip flag * Better todo * Change working from Import to Insert, cleanups * Sister command in kclCommands to populate file options * Improve path selector * Unsure: move importAstMod to kclCommands onSubmit 😶 * Add e2e test * Clean up for review * Add native file menu entry and test * No await yo lint said so * WIP: UX improvements around foreign file imports Fixes #6152 * @lrev-Dev's suggestion to remove a comment Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch> * Update to scene.settled(cmdBar) * Add partNNN default name for alias * Lint * Lint * Fix unit tests * Add sad path insert test Thanks @Irev-Dev for the suggestion * Add step insert test * Lint * Add test for second foreign import thru file tree click * WIP: Add point-and-click Load to copy files from outside the project into the project Towards #6210 * Move Insert button to modeling toolbar, update menus and toolbars * Add default value for local name alias * Aligning tests * Fix tests * Add padding for filenames starting with a digit * Lint * Lint * Update snapshots * Merge branch 'main' into pierremtb/issue6210-Add-point-and-click-Load-to-copy-files-from-outside-the-project-into-the-project * Add disabled transform subbutton * Merge kcl-samples and local disk load into one 'Load external model' command * Fix em tests * Fix test * Add test for file pick import, better input * Fix non .kcl loading * Lint * Update snapshots * Fix issue leading to test failure * Fix clone test * Add note * Fix nested clone issue * Clean up for review * Add valueSummary for path * Fix test after path change * Clean up for review * Update src/lib/kclCommands.ts Thanks @franknoirot! Co-authored-by: Frank Noirot <frank@zoo.dev> * Improve path input arg * Fix tests * Merge branch 'main' into pierremtb/issue6210-Add-point-and-click-Load-to-copy-files-from-outside-the-project-into-the-project * Fix path header not showing and improve tests * Clean up --------- Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch> Co-authored-by: Frank Noirot <frank@zoo.dev>
@ -3,7 +3,7 @@ import { expect } from '@playwright/test'
 | 
			
		||||
import * as fs from 'fs'
 | 
			
		||||
import * as path from 'path'
 | 
			
		||||
 | 
			
		||||
type CmdBarSerialised =
 | 
			
		||||
export type CmdBarSerialised =
 | 
			
		||||
  | {
 | 
			
		||||
      stage: 'commandBarClosed'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ export class ToolbarFixture {
 | 
			
		||||
  offsetPlaneButton!: Locator
 | 
			
		||||
  helixButton!: Locator
 | 
			
		||||
  startSketchBtn!: Locator
 | 
			
		||||
  insertButton!: Locator
 | 
			
		||||
  lineBtn!: Locator
 | 
			
		||||
  tangentialArcBtn!: Locator
 | 
			
		||||
  circleBtn!: Locator
 | 
			
		||||
@ -44,7 +45,7 @@ export class ToolbarFixture {
 | 
			
		||||
  featureTreePane!: Locator
 | 
			
		||||
  gizmo!: Locator
 | 
			
		||||
  gizmoDisabled!: Locator
 | 
			
		||||
  insertButton!: Locator
 | 
			
		||||
  loadButton!: Locator
 | 
			
		||||
 | 
			
		||||
  constructor(page: Page) {
 | 
			
		||||
    this.page = page
 | 
			
		||||
@ -59,6 +60,7 @@ export class ToolbarFixture {
 | 
			
		||||
    this.offsetPlaneButton = page.getByTestId('plane-offset')
 | 
			
		||||
    this.helixButton = page.getByTestId('helix')
 | 
			
		||||
    this.startSketchBtn = page.getByTestId('sketch')
 | 
			
		||||
    this.insertButton = page.getByTestId('insert')
 | 
			
		||||
    this.lineBtn = page.getByTestId('line')
 | 
			
		||||
    this.tangentialArcBtn = page.getByTestId('tangential-arc')
 | 
			
		||||
    this.circleBtn = page.getByTestId('circle-center')
 | 
			
		||||
@ -68,6 +70,7 @@ export class ToolbarFixture {
 | 
			
		||||
    this.fileTreeBtn = page.locator('[id="files-button-holder"]')
 | 
			
		||||
    this.createFileBtn = page.getByTestId('create-file-button')
 | 
			
		||||
    this.treeInputField = page.getByTestId('tree-input-field')
 | 
			
		||||
    this.loadButton = page.getByTestId('load-external-model-pane-button')
 | 
			
		||||
 | 
			
		||||
    this.filePane = page.locator('#files-pane')
 | 
			
		||||
    this.featureTreePane = page.locator('#feature-tree-pane')
 | 
			
		||||
@ -79,8 +82,6 @@ export class ToolbarFixture {
 | 
			
		||||
    // element or two different elements can represent these states.
 | 
			
		||||
    this.gizmo = page.getByTestId('gizmo')
 | 
			
		||||
    this.gizmoDisabled = page.getByTestId('gizmo-disabled')
 | 
			
		||||
 | 
			
		||||
    this.insertButton = page.getByTestId('insert-pane-button')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get logoLink() {
 | 
			
		||||
 | 
			
		||||
@ -534,7 +534,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
 | 
			
		||||
        const expected = 'Open project'
 | 
			
		||||
        expect(actual).toBe(expected)
 | 
			
		||||
      })
 | 
			
		||||
      test('Modeling.File.Load a sample model', async ({
 | 
			
		||||
      test('Modeling.File.Load external model', async ({
 | 
			
		||||
        tronApp,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
        page,
 | 
			
		||||
@ -555,10 +555,10 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
 | 
			
		||||
            throw new Error('app or app.applicationMenu is missing')
 | 
			
		||||
          }
 | 
			
		||||
          const openProject = app.applicationMenu.getMenuItemById(
 | 
			
		||||
            'File.Load a sample model'
 | 
			
		||||
            'File.Load external model'
 | 
			
		||||
          )
 | 
			
		||||
          if (!openProject) {
 | 
			
		||||
            throw new Error('File.Load a sample model')
 | 
			
		||||
            throw new Error('File.Load external model')
 | 
			
		||||
          }
 | 
			
		||||
          openProject.click()
 | 
			
		||||
        })
 | 
			
		||||
@ -568,44 +568,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
 | 
			
		||||
        const actual = await cmdBar.cmdBarElement
 | 
			
		||||
          .getByTestId('command-name')
 | 
			
		||||
          .textContent()
 | 
			
		||||
        const expected = 'Open sample'
 | 
			
		||||
        expect(actual).toBe(expected)
 | 
			
		||||
      })
 | 
			
		||||
      test('Modeling.File.Insert from project file', async ({
 | 
			
		||||
        tronApp,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
        page,
 | 
			
		||||
        homePage,
 | 
			
		||||
        scene,
 | 
			
		||||
      }) => {
 | 
			
		||||
        if (!tronApp) {
 | 
			
		||||
          throwTronAppMissing()
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        await homePage.goToModelingScene()
 | 
			
		||||
        await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
        // Run electron snippet to find the Menu!
 | 
			
		||||
        await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
 | 
			
		||||
        await tronApp.electron.evaluate(async ({ app }) => {
 | 
			
		||||
          if (!app || !app.applicationMenu) {
 | 
			
		||||
            throw new Error('app or app.applicationMenu is missing')
 | 
			
		||||
          }
 | 
			
		||||
          const openProject = app.applicationMenu.getMenuItemById(
 | 
			
		||||
            'File.Insert from project file'
 | 
			
		||||
          )
 | 
			
		||||
          if (!openProject) {
 | 
			
		||||
            throw new Error('File.Insert from project file')
 | 
			
		||||
          }
 | 
			
		||||
          openProject.click()
 | 
			
		||||
        })
 | 
			
		||||
        // Check that the command bar is opened
 | 
			
		||||
        await expect(cmdBar.cmdBarElement).toBeVisible()
 | 
			
		||||
        // Check the placeholder project name exists
 | 
			
		||||
        const actual = await cmdBar.cmdBarElement
 | 
			
		||||
          .getByTestId('command-name')
 | 
			
		||||
          .textContent()
 | 
			
		||||
        const expected = 'Insert'
 | 
			
		||||
        const expected = 'Load external model'
 | 
			
		||||
        expect(actual).toBe(expected)
 | 
			
		||||
      })
 | 
			
		||||
      test('Modeling.File.Export current part', async ({
 | 
			
		||||
@ -2159,6 +2122,44 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
 | 
			
		||||
        expect(actual).toBe(expected)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      test('Modeling.Design.Insert from project file', async ({
 | 
			
		||||
        tronApp,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
        page,
 | 
			
		||||
        homePage,
 | 
			
		||||
        scene,
 | 
			
		||||
      }) => {
 | 
			
		||||
        if (!tronApp) {
 | 
			
		||||
          throwTronAppMissing()
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        await homePage.goToModelingScene()
 | 
			
		||||
        await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
        // Run electron snippet to find the Menu!
 | 
			
		||||
        await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
 | 
			
		||||
        await tronApp.electron.evaluate(async ({ app }) => {
 | 
			
		||||
          if (!app || !app.applicationMenu) {
 | 
			
		||||
            throw new Error('app or app.applicationMenu is missing')
 | 
			
		||||
          }
 | 
			
		||||
          const openProject = app.applicationMenu.getMenuItemById(
 | 
			
		||||
            'Design.Insert from project file'
 | 
			
		||||
          )
 | 
			
		||||
          if (!openProject) {
 | 
			
		||||
            throw new Error('Design.Insert from project file')
 | 
			
		||||
          }
 | 
			
		||||
          openProject.click()
 | 
			
		||||
        })
 | 
			
		||||
        // Check that the command bar is opened
 | 
			
		||||
        await expect(cmdBar.cmdBarElement).toBeVisible()
 | 
			
		||||
        // Check the placeholder project name exists
 | 
			
		||||
        const actual = await cmdBar.cmdBarElement
 | 
			
		||||
          .getByTestId('command-name')
 | 
			
		||||
          .textContent()
 | 
			
		||||
        const expected = 'Insert'
 | 
			
		||||
        expect(actual).toBe(expected)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      test('Modeling.Design.Create with Zoo Text-To-CAD', async ({
 | 
			
		||||
        tronApp,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 52 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 48 KiB  | 
| 
		 Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 55 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 47 KiB  | 
| 
		 Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 43 KiB  | 
| 
		 Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 51 KiB  | 
| 
		 Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 57 KiB  | 
| 
		 Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 62 KiB  | 
| 
		 Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 31 KiB  | 
| 
		 Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB  | 
| 
		 Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 74 KiB  | 
| 
		 Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 47 KiB  | 
| 
		 Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 58 KiB  | 
| 
		 Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 138 KiB  | 
| 
		 Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 121 KiB  | 
| 
		 Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 65 KiB  | 
| 
		 Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 68 KiB  | 
| 
		 Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 64 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB  | 
| 
		 Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 63 KiB  | 
@ -3,14 +3,18 @@ import { FILE_EXT } from '@src/lib/constants'
 | 
			
		||||
import * as fsp from 'fs/promises'
 | 
			
		||||
import { join } from 'path'
 | 
			
		||||
 | 
			
		||||
import type { CmdBarSerialised } from '@e2e/playwright/fixtures/cmdBarFixture'
 | 
			
		||||
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
 | 
			
		||||
import {
 | 
			
		||||
  executorInputPath,
 | 
			
		||||
  getUtils,
 | 
			
		||||
  orRunWhenFullSuiteEnabled,
 | 
			
		||||
  runningOnWindows,
 | 
			
		||||
  testsInputPath,
 | 
			
		||||
} from '@e2e/playwright/test-utils'
 | 
			
		||||
import { expect, test } from '@e2e/playwright/zoo-test'
 | 
			
		||||
 | 
			
		||||
test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
test.describe('Testing loading external models', () => {
 | 
			
		||||
  /**
 | 
			
		||||
   * Note this test implicitly depends on the KCL sample "parametric-bearing-pillow-block",
 | 
			
		||||
   * its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/parametric-bearing-pillow-block/main.kcl
 | 
			
		||||
@ -39,7 +43,7 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
    }
 | 
			
		||||
    const commandBarButton = page.getByRole('button', { name: 'Commands' })
 | 
			
		||||
    const samplesCommandOption = page.getByRole('option', {
 | 
			
		||||
      name: 'Open Sample',
 | 
			
		||||
      name: 'Load external model',
 | 
			
		||||
    })
 | 
			
		||||
    const commandSampleOption = page.getByRole('option', {
 | 
			
		||||
      name: newSample.title,
 | 
			
		||||
@ -83,7 +87,7 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
  test(
 | 
			
		||||
    'Desktop: should create new file by default, optionally overwrite',
 | 
			
		||||
    { tag: '@electron' },
 | 
			
		||||
    async ({ editor, context, page, scene, cmdBar }, testInfo) => {
 | 
			
		||||
    async ({ editor, context, page, scene, cmdBar, toolbar }) => {
 | 
			
		||||
      if (runningOnWindows()) {
 | 
			
		||||
        test.fixme(orRunWhenFullSuiteEnabled())
 | 
			
		||||
      }
 | 
			
		||||
@ -106,20 +110,12 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
        title: '100mm Gear Rack',
 | 
			
		||||
      }
 | 
			
		||||
      const projectCard = page.getByRole('link', { name: 'bracket' })
 | 
			
		||||
      const commandBarButton = page.getByRole('button', { name: 'Commands' })
 | 
			
		||||
      const commandOption = page.getByRole('option', { name: 'Open Sample' })
 | 
			
		||||
      const commandSampleOption = (name: string) =>
 | 
			
		||||
        page.getByRole('option', {
 | 
			
		||||
          name,
 | 
			
		||||
          exact: true,
 | 
			
		||||
        })
 | 
			
		||||
      const commandMethodArgButton = page.getByRole('button', {
 | 
			
		||||
        name: 'Method',
 | 
			
		||||
      })
 | 
			
		||||
      const commandMethodOption = page.getByRole('option', {
 | 
			
		||||
        name: 'Overwrite',
 | 
			
		||||
      })
 | 
			
		||||
      const newFileWarning = page.getByText('Create a new file from sample?')
 | 
			
		||||
      const overwriteWarning = page.getByText(
 | 
			
		||||
        'Overwrite current file with sample?'
 | 
			
		||||
      )
 | 
			
		||||
@ -129,6 +125,18 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
        page.getByRole('listitem').filter({
 | 
			
		||||
          has: page.getByRole('button', { name }),
 | 
			
		||||
        })
 | 
			
		||||
      const defaultLoadCmdBarState: CmdBarSerialised = {
 | 
			
		||||
        commandName: 'Load external model',
 | 
			
		||||
        currentArgKey: 'source',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Method: 'newFile',
 | 
			
		||||
          Sample: '',
 | 
			
		||||
          Source: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'source',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await test.step(`Test setup`, async () => {
 | 
			
		||||
        await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
@ -147,14 +155,12 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step(`Load a KCL sample with the command palette`, async () => {
 | 
			
		||||
        await commandBarButton.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await commandOption.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await commandSampleOption(sampleOne.title).click()
 | 
			
		||||
        await toolbar.loadButton.click()
 | 
			
		||||
        await cmdBar.expectState(defaultLoadCmdBarState)
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
        await cmdBar.selectOption({ name: sampleOne.title }).click()
 | 
			
		||||
        await expect(overwriteWarning).not.toBeVisible()
 | 
			
		||||
        await expect(newFileWarning).toBeVisible()
 | 
			
		||||
        await confirmButton.click()
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -165,21 +171,15 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step(`Now overwrite the current file`, async () => {
 | 
			
		||||
        await commandBarButton.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await commandOption.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await commandSampleOption(sampleTwo.title).click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await toolbar.loadButton.click()
 | 
			
		||||
        await cmdBar.expectState(defaultLoadCmdBarState)
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
        await cmdBar.selectOption({ name: sampleTwo.title }).click()
 | 
			
		||||
        await commandMethodArgButton.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await commandMethodOption.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
        await expect(commandMethodArgButton).toContainText('overwrite')
 | 
			
		||||
        await expect(newFileWarning).not.toBeVisible()
 | 
			
		||||
        await expect(overwriteWarning).toBeVisible()
 | 
			
		||||
        await confirmButton.click()
 | 
			
		||||
        await page.waitForTimeout(1000)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step(`Ensure we overwrote the current file without navigating`, async () => {
 | 
			
		||||
@ -200,4 +200,96 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const externalModelCases = [
 | 
			
		||||
    {
 | 
			
		||||
      modelName: 'cylinder.kcl',
 | 
			
		||||
      deconflictedModelName: 'cylinder-1.kcl',
 | 
			
		||||
      modelPath: executorInputPath('cylinder.kcl'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      modelName: 'cube.step',
 | 
			
		||||
      deconflictedModelName: 'cube-1.step',
 | 
			
		||||
      modelPath: testsInputPath('cube.step'),
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
  externalModelCases.map(({ modelName, deconflictedModelName, modelPath }) => {
 | 
			
		||||
    test(
 | 
			
		||||
      `Load external models from local drive - ${modelName}`,
 | 
			
		||||
      { tag: ['@electron'] },
 | 
			
		||||
      async ({ page, homePage, scene, toolbar, cmdBar, tronApp }) => {
 | 
			
		||||
        if (!tronApp) {
 | 
			
		||||
          fail()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
        await homePage.goToModelingScene()
 | 
			
		||||
        await scene.settled(cmdBar)
 | 
			
		||||
        const modelFileContent = await fsp.readFile(modelPath, 'utf-8')
 | 
			
		||||
        const { editorTextMatches } = await getUtils(page, test)
 | 
			
		||||
 | 
			
		||||
        async function loadExternalFileThroughCommandBar(tronApp: ElectronZoo) {
 | 
			
		||||
          await toolbar.loadButton.click()
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            commandName: 'Load external model',
 | 
			
		||||
            currentArgKey: 'source',
 | 
			
		||||
            currentArgValue: '',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              Method: 'newFile',
 | 
			
		||||
              Sample: '',
 | 
			
		||||
              Source: '',
 | 
			
		||||
            },
 | 
			
		||||
            highlightedHeaderArg: 'source',
 | 
			
		||||
            stage: 'arguments',
 | 
			
		||||
          })
 | 
			
		||||
          await cmdBar.selectOption({ name: 'Local Drive' }).click()
 | 
			
		||||
 | 
			
		||||
          // Mock the file picker selection
 | 
			
		||||
          const handleFile = tronApp.electron.evaluate(
 | 
			
		||||
            async ({ dialog }, filePaths) => {
 | 
			
		||||
              dialog.showOpenDialog = () =>
 | 
			
		||||
                Promise.resolve({ canceled: false, filePaths })
 | 
			
		||||
            },
 | 
			
		||||
            [modelPath]
 | 
			
		||||
          )
 | 
			
		||||
          await page.getByTestId('cmd-bar-arg-file-button').click()
 | 
			
		||||
          await handleFile
 | 
			
		||||
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            commandName: 'Load external model',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              Source: 'local',
 | 
			
		||||
              Path: modelName,
 | 
			
		||||
            },
 | 
			
		||||
            stage: 'review',
 | 
			
		||||
          })
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await test.step('Load the external model from local drive', async () => {
 | 
			
		||||
          await loadExternalFileThroughCommandBar(tronApp)
 | 
			
		||||
          // TODO: I think the files pane should auto open?
 | 
			
		||||
          await toolbar.openPane('files')
 | 
			
		||||
          await toolbar.expectFileTreeState([modelName, 'main.kcl'])
 | 
			
		||||
          if (modelName.endsWith('.kcl')) {
 | 
			
		||||
            await editorTextMatches(modelFileContent)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        await test.step('Load the same external model, except deconflicted name', async () => {
 | 
			
		||||
          await loadExternalFileThroughCommandBar(tronApp)
 | 
			
		||||
          await toolbar.openPane('files')
 | 
			
		||||
          await toolbar.expectFileTreeState([
 | 
			
		||||
            deconflictedModelName,
 | 
			
		||||
            modelName,
 | 
			
		||||
            'main.kcl',
 | 
			
		||||
          ])
 | 
			
		||||
          if (modelName.endsWith('.kcl')) {
 | 
			
		||||
            await editorTextMatches(modelFileContent)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||