File tree stuff (#3679)
* Fix and test file tree operations * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * Make tsc happy * I've been lied to * Fix navigating to deleted file * tsc * Remove debugger statement * Fix test * All tests fixed * Remove old config and remove slowmo * fmt * Remove unintentional changelog in readme (#3678) * lint * fmt * Increase test timeout * Fix the damn test * fix web app * fmt --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jonathan Tran <jonnytran@gmail.com> Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
204
e2e/playwright/file-tree.spec.ts
Normal file
204
e2e/playwright/file-tree.spec.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
import * as fsp from 'fs/promises'
|
||||||
|
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||||
|
|
||||||
|
test.beforeEach(async ({ context, page }) => {
|
||||||
|
await setup(context, page)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
await tearDown(page, testInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('when using the file tree to', () => {
|
||||||
|
const fromFile = 'main.kcl'
|
||||||
|
const toFile = 'hello.kcl'
|
||||||
|
|
||||||
|
test(
|
||||||
|
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
renameFile,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files', 'code'])
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
|
// File the main.kcl with contents
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
await renameFile(fromFile, toFile)
|
||||||
|
await page.reload()
|
||||||
|
|
||||||
|
await test.step('Postcondition: editor has same content as before the rename', async () => {
|
||||||
|
await editorTextMatches(kclCube)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Postcondition: opening and closing settings works', async () => {
|
||||||
|
const settingsOpenButton = page.getByRole('link', {
|
||||||
|
name: 'settings Settings',
|
||||||
|
})
|
||||||
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
await settingsOpenButton.click()
|
||||||
|
await settingsCloseButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`create many new untitled files they increment their names`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { panesOpen, createAndSelectProject, createNewFile } =
|
||||||
|
await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files'])
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
|
await createNewFile('')
|
||||||
|
await createNewFile('')
|
||||||
|
await createNewFile('')
|
||||||
|
await createNewFile('')
|
||||||
|
await createNewFile('')
|
||||||
|
|
||||||
|
await test.step('Postcondition: there are 5 new Untitled-*.kcl files', async () => {
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: /Untitled[-]?[0-5]?/ })
|
||||||
|
).toHaveCount(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'create a new file with the same name as an existing file cancels the operation',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
createNewFileAndSelect,
|
||||||
|
renameFile,
|
||||||
|
selectFile,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files', 'code'])
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
// File the main.kcl with contents
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
const kcl1 = 'main.kcl'
|
||||||
|
const kcl2 = '2.kcl'
|
||||||
|
|
||||||
|
await createNewFileAndSelect(kcl2)
|
||||||
|
const kclCylinder = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCylinder)
|
||||||
|
|
||||||
|
await renameFile(kcl2, kcl1)
|
||||||
|
|
||||||
|
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
|
||||||
|
await selectFile(kcl1)
|
||||||
|
await editorTextMatches(kclCube)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||||
|
await selectFile(kcl2)
|
||||||
|
await editorTextMatches(kclCylinder)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'deleting all files recreates a default main.kcl with no code',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
deleteFile,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen(['files', 'code'])
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
// File the main.kcl with contents
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
const kcl1 = 'main.kcl'
|
||||||
|
|
||||||
|
await deleteFile(kcl1)
|
||||||
|
|
||||||
|
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
|
||||||
|
await editorTextMatches('')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
@ -511,10 +511,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
|
|
||||||
editorTextMatches: async (code: string) => {
|
editorTextMatches: async (code: string) => {
|
||||||
const editor = page.locator(editorSelector)
|
const editor = page.locator(editorSelector)
|
||||||
const editorText = await editor.textContent()
|
return expect(editor).toHaveText(code, { useInnerText: true })
|
||||||
return expect(util.toNormalizedCode(editorText || '')).toBe(
|
|
||||||
util.toNormalizedCode(code)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
pasteCodeInEditor: async (code: string) => {
|
pasteCodeInEditor: async (code: string) => {
|
||||||
@ -532,18 +529,62 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createNewFile: async (name: string) => {
|
||||||
|
return test?.step(`Create a file named ${name}`, async () => {
|
||||||
|
await page.getByTestId('create-file-button').click()
|
||||||
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
selectFile: async (name: string) => {
|
||||||
|
return test?.step(`Select ${name}`, async () => {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: name })
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
createNewFileAndSelect: async (name: string) => {
|
createNewFileAndSelect: async (name: string) => {
|
||||||
return test?.step(`Create a file named ${name}, select it`, async () => {
|
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||||
await page.getByTestId('create-file-button').click()
|
await page.getByTestId('create-file-button').click()
|
||||||
await page.getByTestId('file-rename-field').fill(name)
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page
|
await page
|
||||||
.getByTestId('file-pane-scroll-container')
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
.filter({ hasText: name })
|
.filter({ hasText: name })
|
||||||
.click()
|
.click()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renameFile: async (fromName: string, toName: string) => {
|
||||||
|
return test?.step(`Rename ${fromName} to ${toName}`, async () => {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: fromName })
|
||||||
|
.click({ button: 'right' })
|
||||||
|
await page.getByTestId('context-menu-rename').click()
|
||||||
|
await page.getByTestId('file-rename-field').fill(toName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: toName })
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteFile: async (name: string) => {
|
||||||
|
return test?.step(`Delete ${name}`, async () => {
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
|
.filter({ hasText: name })
|
||||||
|
.click({ button: 'right' })
|
||||||
|
await page.getByTestId('context-menu-delete').click()
|
||||||
|
await page.getByTestId('delete-confirmation').click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
panesOpen: async (paneIds: PaneId[]) => {
|
panesOpen: async (paneIds: PaneId[]) => {
|
||||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page } from '@playwright/test'
|
||||||
import {
|
import { getUtils, setup, tearDown, setupElectron } from './test-utils'
|
||||||
getUtils,
|
|
||||||
setup,
|
|
||||||
tearDown,
|
|
||||||
setupElectron,
|
|
||||||
createProjectAndRenameIt,
|
|
||||||
} from './test-utils'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
@ -698,13 +692,16 @@ test(
|
|||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
const { electronApp, page, dir } = await setupElectron({ testInfo })
|
||||||
const fileExists = () =>
|
const fileExists = () =>
|
||||||
fs.existsSync(join(dir, 'test-000', 'lego-2x4.kcl'))
|
fs.existsSync(join(dir, 'project-000', 'lego-2x4.kcl'))
|
||||||
|
|
||||||
|
const { createAndSelectProject, panesOpen } = await getUtils(page, test)
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await panesOpen(['code', 'files'])
|
||||||
|
|
||||||
// Create and navigate to the project
|
// Create and navigate to the project
|
||||||
await createProjectAndRenameIt({ name: 'test-000', page })
|
await createAndSelectProject('project-000')
|
||||||
await page.getByTestId('project-link').click()
|
|
||||||
|
|
||||||
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
|
||||||
await expect(
|
await expect(
|
||||||
@ -713,10 +710,6 @@ test(
|
|||||||
timeout: 20_000,
|
timeout: 20_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Open the files pane
|
|
||||||
const filesPaneButton = page.getByTestId('files-pane-button')
|
|
||||||
await filesPaneButton.click()
|
|
||||||
|
|
||||||
await test.step(`Test file creation`, async () => {
|
await test.step(`Test file creation`, async () => {
|
||||||
await sendPromptFromCommandBar(page, 'lego 2x4')
|
await sendPromptFromCommandBar(page, 'lego 2x4')
|
||||||
// File is considered created if it shows up in the Project Files pane
|
// File is considered created if it shows up in the Project Files pane
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test'
|
|
||||||
import dotenv from 'dotenv'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
timeout: 120_000, // override the default 30s timeout
|
|
||||||
testDir: './e2e/playwright',
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Do not retry */
|
|
||||||
retries: process.env.CI ? 0 : 0,
|
|
||||||
/* Different amount of parallelism on CI and local. */
|
|
||||||
workers: process.env.CI ? 1 : 4,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: [
|
|
||||||
[process.env.CI ? 'dot' : 'list'],
|
|
||||||
['json', { outputFile: './test-results/report.json' }],
|
|
||||||
['html'],
|
|
||||||
],
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: 'retain-on-failure',
|
|
||||||
actionTimeout: 15000,
|
|
||||||
screenshot: 'only-on-failure',
|
|
||||||
},
|
|
||||||
})
|
|
@ -135,16 +135,15 @@ interface ContextMenuItemProps {
|
|||||||
icon?: ActionIconProps['icon']
|
icon?: ActionIconProps['icon']
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
hotkey?: string
|
hotkey?: string
|
||||||
|
'data-testid'?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContextMenuItem({
|
export function ContextMenuItem(props: ContextMenuItemProps) {
|
||||||
children,
|
const { children, icon, onClick, hotkey } = props
|
||||||
icon,
|
|
||||||
onClick,
|
|
||||||
hotkey,
|
|
||||||
}: ContextMenuItemProps) {
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
data-testid={props['data-testid']}
|
||||||
className="flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
className="flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
|
@ -16,7 +16,11 @@ import {
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { fileMachine } from 'machines/fileMachine'
|
import { fileMachine } from 'machines/fileMachine'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
|
import {
|
||||||
|
DEFAULT_FILE_NAME,
|
||||||
|
DEFAULT_PROJECT_KCL_FILE,
|
||||||
|
FILE_EXT,
|
||||||
|
} from 'lib/constants'
|
||||||
import { getProjectInfo } from 'lib/desktop'
|
import { getProjectInfo } from 'lib/desktop'
|
||||||
import { getNextDirName, getNextFileName } from 'lib/desktopFS'
|
import { getNextDirName, getNextFileName } from 'lib/desktopFS'
|
||||||
|
|
||||||
@ -167,6 +171,25 @@ export const FileMachineProvider = ({
|
|||||||
name
|
name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// no-op
|
||||||
|
if (oldPath === newPath) {
|
||||||
|
return {
|
||||||
|
message: `Old is the same as new.`,
|
||||||
|
newPath,
|
||||||
|
oldPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are any siblings with the same name, report error.
|
||||||
|
const entries = await window.electron.readdir(
|
||||||
|
window.electron.path.dirname(newPath)
|
||||||
|
)
|
||||||
|
for (let entry of entries) {
|
||||||
|
if (entry === newName) {
|
||||||
|
return Promise.reject(new Error('Filename already exists.'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.electron.rename(oldPath, newPath)
|
window.electron.rename(oldPath, newPath)
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@ -209,6 +232,27 @@ export const FileMachineProvider = ({
|
|||||||
.catch((e) => console.error('Error deleting file', e))
|
.catch((e) => console.error('Error deleting file', e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are no more files at all in the project, create a main.kcl
|
||||||
|
// for when we navigate to the root.
|
||||||
|
if (!project?.path) {
|
||||||
|
return Promise.reject(new Error('Project path not set.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = await window.electron.readdir(project.path)
|
||||||
|
const hasKclEntries =
|
||||||
|
entries.filter((e: string) => e.endsWith('.kcl')).length !== 0
|
||||||
|
if (!hasKclEntries) {
|
||||||
|
await window.electron.writeFile(
|
||||||
|
window.electron.path.join(project.path, DEFAULT_PROJECT_KCL_FILE),
|
||||||
|
''
|
||||||
|
)
|
||||||
|
// Refresh the route selected above because it's possible we're on
|
||||||
|
// the same path on the navigate, which doesn't cause anything to
|
||||||
|
// refresh, leaving a stale execution state.
|
||||||
|
navigate(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we just deleted the current file or one of its parent directories,
|
// If we just deleted the current file or one of its parent directories,
|
||||||
// navigate to the project root
|
// navigate to the project root
|
||||||
if (
|
if (
|
||||||
|
@ -358,10 +358,18 @@ function FileTreeContextMenu({
|
|||||||
<ContextMenu
|
<ContextMenu
|
||||||
menuTargetElement={itemRef}
|
menuTargetElement={itemRef}
|
||||||
items={[
|
items={[
|
||||||
<ContextMenuItem onClick={onRename} hotkey="Enter">
|
<ContextMenuItem
|
||||||
|
data-testid="context-menu-rename"
|
||||||
|
onClick={onRename}
|
||||||
|
hotkey="Enter"
|
||||||
|
>
|
||||||
Rename
|
Rename
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
<ContextMenuItem onClick={onDelete} hotkey={metaKey + ' + Del'}>
|
<ContextMenuItem
|
||||||
|
data-testid="context-menu-delete"
|
||||||
|
onClick={onDelete}
|
||||||
|
hotkey={metaKey + ' + Del'}
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
]}
|
]}
|
||||||
|
@ -152,7 +152,7 @@ const extrude001 = extrude(-15, sketch001)`
|
|||||||
selectedSegmentSnippet,
|
selectedSegmentSnippet,
|
||||||
expectedExtrudeSnippet
|
expectedExtrudeSnippet
|
||||||
)
|
)
|
||||||
})
|
}, 5_000)
|
||||||
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
|
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
|
||||||
const code = `const sketch001 = startSketchOn('XY')
|
const code = `const sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-30, 30], %)
|
|> startProfileAt([-30, 30], %)
|
||||||
|
@ -8,6 +8,7 @@ export const MAX_PADDING = 7
|
|||||||
* This is available for users to edit as a setting.
|
* This is available for users to edit as a setting.
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
||||||
|
export const DEFAULT_PROJECT_KCL_FILE = 'main.kcl'
|
||||||
/** Name given the temporary "project" in the browser version of the app */
|
/** Name given the temporary "project" in the browser version of the app */
|
||||||
export const BROWSER_PROJECT_NAME = 'browser'
|
export const BROWSER_PROJECT_NAME = 'browser'
|
||||||
/** Name given the temporary file in the browser version of the app */
|
/** Name given the temporary file in the browser version of the app */
|
||||||
|
@ -90,12 +90,24 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
let code = ''
|
let code = ''
|
||||||
|
|
||||||
if (!urlObj.pathname.endsWith('/settings')) {
|
if (!urlObj.pathname.endsWith('/settings')) {
|
||||||
if (!currentFileName || !currentFilePath || !projectName) {
|
const fallbackFile = isDesktop()
|
||||||
|
? (await getProjectInfo(projectPath)).default_file
|
||||||
|
: ''
|
||||||
|
let fileExists = isDesktop()
|
||||||
|
if (currentFilePath && fileExists) {
|
||||||
|
try {
|
||||||
|
await window.electron.stat(currentFilePath)
|
||||||
|
} catch (e) {
|
||||||
|
if (e === 'ENOENT') {
|
||||||
|
fileExists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileExists || !currentFileName || !currentFilePath || !projectName) {
|
||||||
return redirect(
|
return redirect(
|
||||||
`${PATHS.FILE}/${encodeURIComponent(
|
`${PATHS.FILE}/${encodeURIComponent(
|
||||||
isDesktop()
|
isDesktop() ? fallbackFile : params.id + '/' + PROJECT_ENTRYPOINT
|
||||||
? (await getProjectInfo(projectPath)).default_file
|
|
||||||
: params.id + '/' + PROJECT_ENTRYPOINT
|
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user