Fix path splitting issues on windows (#3565)
* Fix path splitting issues on windows * Fix path splitting issue on routeLoaders * Enable some e2e tests * Swap enabled e2e tests * Working bare-min project parse * Make tsc happy * Clean up & enable more tests * Fix paths in browser * Fix tests for windows fmt * Clean up wasm side * Make build:wasm windows compatible * More paths cleanup & some tests * Remove sleep * Use new config sturcture in parseroute * Clean up debugger * Fix: on settings close go back to the same file (#3549) * Fix: on settings close go back to the same file * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * shit aint working yo * Get that page a-loading * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Frank Noirot <frank@kittycad.io> Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com> * Fmt * Comment out currently failing win32 tests * Ignore tsc for electron monkey-patch * Force line-endings to only * Fix tsc * Enable more tests * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) * Avoid modifying global for tests --------- Co-authored-by: 49fl <ircsurfer33@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Frank Noirot <frank@kittycad.io> Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
This commit is contained in:
@ -45,17 +45,20 @@ test(
|
|||||||
'click help/keybindings from project page',
|
'click help/keybindings from project page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
await fsp.mkdir(join(dir, 'bracket'), { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
join(
|
||||||
`${dir}/bracket/main.kcl`
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'focusrite_scarlett_mounting_braket.kcl'
|
||||||
|
),
|
||||||
|
join(dir, 'bracket', 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -64,8 +67,6 @@ test(
|
|||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
// expect to see the text bracket
|
// expect to see the text bracket
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
|
|
||||||
@ -92,17 +93,20 @@ test(
|
|||||||
'when code with error first loads you get errors in console',
|
'when code with error first loads you get errors in console',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/broken-code`, { recursive: true })
|
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/broken-code-test.kcl',
|
join(
|
||||||
`${dir}/broken-code/main.kcl`
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'broken-code-test.kcl'
|
||||||
|
),
|
||||||
|
join(dir, 'broken-code', 'main.kcl')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -232,10 +236,6 @@ test(
|
|||||||
'Rename and delete projects, also spam arrow keys when renaming',
|
'Rename and delete projects, also spam arrow keys when renaming',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
@ -524,10 +524,6 @@ test(
|
|||||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
})
|
})
|
||||||
@ -622,10 +618,6 @@ test(
|
|||||||
'Can sort projects on home page',
|
'Can sort projects on home page',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
})
|
})
|
||||||
@ -748,10 +740,6 @@ test(
|
|||||||
'When the project folder is empty, user can create new project and open it.',
|
'When the project folder is empty, user can create new project and open it.',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({ testInfo })
|
const { electronApp, page } = await setupElectron({ testInfo })
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -836,25 +824,35 @@ test(
|
|||||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
folderSetupFn: async (dir) => {
|
folderSetupFn: async (dir) => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
fsp.mkdir(join(dir, 'router-template-slate'), { recursive: true }),
|
||||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
fsp.mkdir(join(dir, 'bracket'), { recursive: true }),
|
||||||
])
|
])
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
join(
|
||||||
`${dir}/router-template-slate/main.kcl`
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'router-template-slate.kcl'
|
||||||
|
),
|
||||||
|
join(dir, 'router-template-slate', 'main.kcl')
|
||||||
),
|
),
|
||||||
fsp.copyFile(
|
fsp.copyFile(
|
||||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
join(
|
||||||
`${dir}/bracket/main.kcl`
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs',
|
||||||
|
'focusrite_scarlett_mounting_braket.kcl'
|
||||||
|
),
|
||||||
|
join(dir, 'bracket', 'main.kcl')
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@ -1302,10 +1300,6 @@ test(
|
|||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ browserName }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
test.skip(
|
|
||||||
process.platform === 'win32',
|
|
||||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
|
||||||
)
|
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
const { electronApp, page } = await setupElectron({
|
const { electronApp, page } = await setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
@ -1682,6 +1676,7 @@ test.describe('Renaming in the file tree', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Rename the folder', async () => {
|
await test.step('Rename the folder', async () => {
|
||||||
|
await page.waitForTimeout(60000)
|
||||||
await folderToRename.click({ button: 'right' })
|
await folderToRename.click({ button: 'right' })
|
||||||
await expect(renameMenuItem).toBeVisible()
|
await expect(renameMenuItem).toBeVisible()
|
||||||
await renameMenuItem.click()
|
await renameMenuItem.click()
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
@ -2,10 +2,11 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
Page,
|
Page,
|
||||||
Download,
|
Download,
|
||||||
TestInfo,
|
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
|
TestInfo,
|
||||||
_electron as electron,
|
_electron as electron,
|
||||||
Locator,
|
Locator,
|
||||||
|
test,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
@ -44,6 +45,9 @@ export const commonPoints = {
|
|||||||
num2: 14.44,
|
num2: 14.44,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||||
|
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||||
|
|
||||||
async function waitForPageLoadWithRetry(page: Page) {
|
async function waitForPageLoadWithRetry(page: Page) {
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
@ -311,13 +315,19 @@ export function normaliseKclNumbers(code: string, ignoreZero = true): string {
|
|||||||
return replaceNumbers(code)
|
return replaceNumbers(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUtils(page: Page) {
|
export async function getUtils(page: Page, test_?: typeof test) {
|
||||||
|
if (!test) {
|
||||||
|
console.warn(
|
||||||
|
'Some methods in getUtils requires test object as second argument'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Chrome devtools protocol session only works in Chromium
|
// Chrome devtools protocol session only works in Chromium
|
||||||
const browserType = page.context().browser()?.browserType().name()
|
const browserType = page.context().browser()?.browserType().name()
|
||||||
const cdpSession =
|
const cdpSession =
|
||||||
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||||
|
|
||||||
return {
|
const util = {
|
||||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||||
waitForPageLoad: () => waitForPageLoad(page),
|
waitForPageLoad: () => waitForPageLoad(page),
|
||||||
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||||
@ -484,7 +494,74 @@ export async function getUtils(page: Page) {
|
|||||||
networkOptions
|
networkOptions
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toNormalizedCode: (text: string) => {
|
||||||
|
return text.replace(/\s+/g, '')
|
||||||
|
},
|
||||||
|
|
||||||
|
createAndSelectProject: async (hasText: string) => {
|
||||||
|
return test_?.step(
|
||||||
|
`Create and select project with text "${hasText}"`,
|
||||||
|
async () => {
|
||||||
|
await page.getByTestId('home-new-file').click()
|
||||||
|
const projectLinksPost = page.getByTestId('project-link')
|
||||||
|
await projectLinksPost.filter({ hasText }).click()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
editorTextMatches: async (code: string) => {
|
||||||
|
const editor = page.locator(editorSelector)
|
||||||
|
const editorText = await editor.textContent()
|
||||||
|
return expect(util.toNormalizedCode(editorText || '')).toBe(
|
||||||
|
util.toNormalizedCode(code)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
pasteCodeInEditor: async (code: string) => {
|
||||||
|
return test?.step('Paste in KCL code', async () => {
|
||||||
|
const editor = page.locator(editorSelector)
|
||||||
|
await editor.fill(code)
|
||||||
|
await util.editorTextMatches(code)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clickPane: async (paneId: PaneId) => {
|
||||||
|
return test?.step(`Open ${paneId} pane`, async () => {
|
||||||
|
await page.getByTestId(paneId + '-pane-button').click()
|
||||||
|
await expect(page.locator('#' + paneId + '-pane')).toBeVisible()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
createNewFileAndSelect: async (name: string) => {
|
||||||
|
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||||
|
await page.getByTestId('create-file-button').click()
|
||||||
|
await page.getByTestId('file-rename-field').fill(name)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page
|
||||||
|
.getByTestId('file-pane-scroll-container')
|
||||||
|
.filter({ hasText: name })
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
panesOpen: async (paneIds: PaneId[]) => {
|
||||||
|
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||||
|
await page.addInitScript(
|
||||||
|
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
PERSIST_MODELING_CONTEXT,
|
||||||
|
JSON.stringify({ openPanes: paneIds })
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ PERSIST_MODELING_CONTEXT, paneIds }
|
||||||
|
)
|
||||||
|
await page.reload()
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return util
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateOptions = Array<number | Array<number>>
|
type TemplateOptions = Array<number | Array<number>>
|
||||||
@ -733,6 +810,7 @@ export async function setup(context: BrowserContext, page: Page) {
|
|||||||
// kill animations, speeds up tests and reduced flakiness
|
// kill animations, speeds up tests and reduced flakiness
|
||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
|
|
||||||
|
// Trigger a navigation, since loading file:// doesn't.
|
||||||
await page.reload()
|
await page.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,4 +264,69 @@ test.describe('Testing settings', () => {
|
|||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`Closing settings modal should go back to the original file being viewed`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
panesOpen,
|
||||||
|
createAndSelectProject,
|
||||||
|
pasteCodeInEditor,
|
||||||
|
clickPane,
|
||||||
|
createNewFileAndSelect,
|
||||||
|
editorTextMatches,
|
||||||
|
} = await getUtils(page, test)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
await panesOpen([])
|
||||||
|
|
||||||
|
await test.step('Precondition: No projects exist', async () => {
|
||||||
|
await expect(page.getByTestId('home-section')).toBeVisible()
|
||||||
|
const projectLinksPre = page.getByTestId('project-link')
|
||||||
|
await expect(projectLinksPre).toHaveCount(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
await createAndSelectProject('project-000')
|
||||||
|
|
||||||
|
await clickPane('code')
|
||||||
|
const kclCube = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCube)
|
||||||
|
|
||||||
|
await clickPane('files')
|
||||||
|
await createNewFileAndSelect('2.kcl')
|
||||||
|
|
||||||
|
const kclCylinder = await fsp.readFile(
|
||||||
|
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
await pasteCodeInEditor(kclCylinder)
|
||||||
|
|
||||||
|
const settingsOpenButton = page.getByRole('link', {
|
||||||
|
name: 'settings Settings',
|
||||||
|
})
|
||||||
|
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||||
|
|
||||||
|
await test.step('Open and close settings', async () => {
|
||||||
|
await settingsOpenButton.click()
|
||||||
|
await settingsCloseButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
|
||||||
|
await editorTextMatches(kclCylinder)
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||||
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"build:wasm": "(cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm": "cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from '@playwright/test'
|
import { defineConfig, devices } from '@playwright/test'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
@ -30,4 +30,24 @@ export default defineConfig({
|
|||||||
actionTimeout: 15_000,
|
actionTimeout: 15_000,
|
||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
},
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
channel: 'chrome',
|
||||||
|
contextOptions: {
|
||||||
|
/* Chromium is the only one with these permission types */
|
||||||
|
permissions: ['clipboard-write', 'clipboard-read'],
|
||||||
|
},
|
||||||
|
launchOptions: {
|
||||||
|
...(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
|
||||||
|
? {
|
||||||
|
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
}, // or 'chrome-beta'
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
@ -61,6 +61,7 @@ function RenameForm({
|
|||||||
<label>
|
<label>
|
||||||
<span className="sr-only">Rename file</span>
|
<span className="sr-only">Rename file</span>
|
||||||
<input
|
<input
|
||||||
|
data-testid="file-rename-field"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
autoFocus
|
autoFocus
|
||||||
@ -402,6 +403,7 @@ export const FileTreeMenu = () => {
|
|||||||
<>
|
<>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
|
data-testid="create-file-button"
|
||||||
iconStart={{
|
iconStart={{
|
||||||
icon: 'filePlus',
|
icon: 'filePlus',
|
||||||
iconClassName: '!text-current',
|
iconClassName: '!text-current',
|
||||||
@ -417,6 +419,7 @@ export const FileTreeMenu = () => {
|
|||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
|
data-testid="create-folder-button"
|
||||||
iconStart={{
|
iconStart={{
|
||||||
icon: 'folderPlus',
|
icon: 'folderPlus',
|
||||||
iconClassName: '!text-current',
|
iconClassName: '!text-current',
|
||||||
|
@ -16,7 +16,6 @@ import init, {
|
|||||||
parse_app_settings,
|
parse_app_settings,
|
||||||
parse_project_settings,
|
parse_project_settings,
|
||||||
default_project_settings,
|
default_project_settings,
|
||||||
parse_project_route,
|
|
||||||
base64_decode,
|
base64_decode,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
@ -33,7 +32,6 @@ import { CoreDumpManager } from 'lib/coredump'
|
|||||||
import openWindow from 'lib/openWindow'
|
import openWindow from 'lib/openWindow'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
import { DeepPartial } from 'lib/types'
|
import { DeepPartial } from 'lib/types'
|
||||||
@ -611,13 +609,6 @@ export function parseProjectSettings(
|
|||||||
return parse_project_settings(toml)
|
return parse_project_settings(toml)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseProjectRoute(
|
|
||||||
configuration: DeepPartial<Configuration>,
|
|
||||||
route_str: string
|
|
||||||
): ProjectRoute | Error {
|
|
||||||
return parse_project_route(JSON.stringify(configuration), route_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function base64Decode(base64: string): ArrayBuffer | Error {
|
export function base64Decode(base64: string): ArrayBuffer | Error {
|
||||||
try {
|
try {
|
||||||
const decoded = base64_decode(base64)
|
const decoded = base64_decode(base64)
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
import { DeepPartial } from './types'
|
import { DeepPartial } from './types'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
export { parseProjectRoute } from 'lang/wasm'
|
|
||||||
|
|
||||||
export async function renameProjectDirectory(
|
export async function renameProjectDirectory(
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
@ -39,7 +38,7 @@ export async function renameProjectDirectory(
|
|||||||
|
|
||||||
// Make sure the new name does not exist.
|
// Make sure the new name does not exist.
|
||||||
const newPath = window.electron.path.join(
|
const newPath = window.electron.path.join(
|
||||||
projectPath.split('/').slice(0, -1).join('/'),
|
window.electron.path.dirname(projectPath),
|
||||||
newName
|
newName
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
@ -186,9 +185,9 @@ const collectAllFilesRecursiveFrom = async (path: string) => {
|
|||||||
return Promise.reject(new Error(`Path ${path} is not a directory`))
|
return Promise.reject(new Error(`Path ${path} is not a directory`))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathParts = path.split(window.electron.path.sep)
|
const name = window.electron.path.basename(path)
|
||||||
let entry: FileEntry = {
|
let entry: FileEntry = {
|
||||||
name: pathParts.slice(-1)[0],
|
name: name,
|
||||||
path,
|
path,
|
||||||
children: [],
|
children: [],
|
||||||
}
|
}
|
||||||
@ -330,7 +329,6 @@ export async function getProjectInfo(projectPath: string): Promise<Project> {
|
|||||||
new Error(`Project path is not a directory: ${projectPath}`)
|
new Error(`Project path is not a directory: ${projectPath}`)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let walked = await collectAllFilesRecursiveFrom(projectPath)
|
let walked = await collectAllFilesRecursiveFrom(projectPath)
|
||||||
let default_file = await getDefaultKclFileForDir(projectPath, walked)
|
let default_file = await getDefaultKclFileForDir(projectPath, walked)
|
||||||
const metadata = await window.electron.stat(projectPath)
|
const metadata = await window.electron.stat(projectPath)
|
||||||
|
79
src/lib/paths.test.ts
Normal file
79
src/lib/paths.test.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { parseProjectRoute } from './paths'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
describe('testing parseProjectRoute', () => {
|
||||||
|
it('should parse a project as a subpath of project dir', async () => {
|
||||||
|
let config = {
|
||||||
|
settings: {
|
||||||
|
project: {
|
||||||
|
directory: '/home/somebody/projects',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const route = '/home/somebody/projects/project'
|
||||||
|
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||||
|
projectName: 'project',
|
||||||
|
projectPath: route,
|
||||||
|
currentFileName: null,
|
||||||
|
currentFilePath: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should parse a project as the project dir', async () => {
|
||||||
|
let config = {
|
||||||
|
settings: {
|
||||||
|
project: {
|
||||||
|
directory: '/home/somebody/projects',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const route = '/home/somebody/projects'
|
||||||
|
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||||
|
projectName: null,
|
||||||
|
projectPath: route,
|
||||||
|
currentFileName: null,
|
||||||
|
currentFilePath: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should parse a project with file in the project dir', async () => {
|
||||||
|
let config = {
|
||||||
|
settings: {
|
||||||
|
project: {
|
||||||
|
directory: '/home/somebody/projects',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const route = '/home/somebody/projects/assembly/main.kcl'
|
||||||
|
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||||
|
projectName: 'assembly',
|
||||||
|
projectPath: '/home/somebody/projects/assembly',
|
||||||
|
currentFileName: 'main.kcl',
|
||||||
|
currentFilePath: route,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should parse a project with file in a subdir in the project dir', async () => {
|
||||||
|
let config = {
|
||||||
|
settings: {
|
||||||
|
project: {
|
||||||
|
directory: '/home/somebody/projects',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const route = '/home/somebody/projects/assembly/subdir/main.kcl'
|
||||||
|
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||||
|
projectName: 'assembly',
|
||||||
|
projectPath: '/home/somebody/projects/assembly',
|
||||||
|
currentFileName: 'main.kcl',
|
||||||
|
currentFilePath: route,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should work in the browser context', async () => {
|
||||||
|
let config = {}
|
||||||
|
const route = '/browser/main.kcl'
|
||||||
|
expect(await parseProjectRoute(config, route, undefined)).toEqual({
|
||||||
|
projectName: 'browser',
|
||||||
|
projectPath: '/browser',
|
||||||
|
currentFileName: 'main.kcl',
|
||||||
|
currentFilePath: route,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,13 +1,13 @@
|
|||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { BROWSER_FILE_NAME, BROWSER_PROJECT_NAME, FILE_EXT } from './constants'
|
import { BROWSER_FILE_NAME, BROWSER_PROJECT_NAME, FILE_EXT } from './constants'
|
||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
import { readAppSettingsFile } from './desktop'
|
||||||
import { parseProjectRoute, readAppSettingsFile } from './desktop'
|
|
||||||
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
||||||
import { DeepPartial } from './types'
|
import { DeepPartial } from './types'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
|
import { PlatformPath } from 'path'
|
||||||
|
|
||||||
const prependRoutes =
|
const prependRoutes =
|
||||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||||
@ -25,6 +25,13 @@ type OnboardingPaths = {
|
|||||||
|
|
||||||
const SETTINGS = '/settings' as const
|
const SETTINGS = '/settings' as const
|
||||||
|
|
||||||
|
export type ProjectRoute = {
|
||||||
|
projectName: string | null
|
||||||
|
projectPath: string
|
||||||
|
currentFileName: string | null
|
||||||
|
currentFilePath: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export const PATHS = {
|
export const PATHS = {
|
||||||
INDEX: '/',
|
INDEX: '/',
|
||||||
HOME: '/home',
|
HOME: '/home',
|
||||||
@ -60,9 +67,64 @@ export async function getProjectMetaByRouteId(
|
|||||||
return Promise.reject(new Error('No configuration found'))
|
return Promise.reject(new Error('No configuration found'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = parseProjectRoute(configuration, id)
|
const route = parseProjectRoute(configuration, id, window?.electron?.path)
|
||||||
|
|
||||||
if (err(route)) return Promise.reject(route)
|
if (err(route)) return Promise.reject(route)
|
||||||
|
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function parseProjectRoute(
|
||||||
|
configuration: DeepPartial<Configuration>,
|
||||||
|
id: string,
|
||||||
|
pathlib: PlatformPath | undefined
|
||||||
|
): Promise<ProjectRoute> {
|
||||||
|
let projectName = null
|
||||||
|
let projectPath = ''
|
||||||
|
let currentFileName = null
|
||||||
|
let currentFilePath = null
|
||||||
|
if (
|
||||||
|
pathlib &&
|
||||||
|
configuration.settings?.project?.directory &&
|
||||||
|
id.startsWith(configuration.settings.project.directory)
|
||||||
|
) {
|
||||||
|
const relativeToRoot = pathlib.relative(
|
||||||
|
configuration.settings.project.directory,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
projectName = relativeToRoot.split(pathlib.sep)[0]
|
||||||
|
projectPath = pathlib.join(
|
||||||
|
configuration.settings.project.directory,
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
projectName = projectName === '' ? null : projectName
|
||||||
|
} else {
|
||||||
|
projectPath = id
|
||||||
|
if (pathlib) {
|
||||||
|
if (pathlib.extname(id) === '.kcl') {
|
||||||
|
projectPath = pathlib.dirname(id)
|
||||||
|
}
|
||||||
|
projectName = pathlib.basename(projectPath)
|
||||||
|
} else {
|
||||||
|
if (id.endsWith('.kcl')) {
|
||||||
|
projectPath = '/browser'
|
||||||
|
projectName = 'browser'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pathlib) {
|
||||||
|
if (projectPath !== id) {
|
||||||
|
currentFileName = pathlib.basename(id)
|
||||||
|
currentFilePath = id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentFileName = 'main.kcl'
|
||||||
|
currentFilePath = id
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
projectName: projectName,
|
||||||
|
projectPath: projectPath,
|
||||||
|
currentFileName: currentFileName,
|
||||||
|
currentFilePath: currentFilePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,9 +36,9 @@ export const settingsLoader: LoaderFunction = async ({
|
|||||||
configuration
|
configuration
|
||||||
)
|
)
|
||||||
if (projectPathData) {
|
if (projectPathData) {
|
||||||
const { project_path } = projectPathData
|
const { projectPath } = projectPathData
|
||||||
const { settings: s } = await loadAndValidateSettings(
|
const { settings: s } = await loadAndValidateSettings(
|
||||||
project_path || undefined
|
projectPath || undefined
|
||||||
)
|
)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@ -83,48 +83,49 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
||||||
|
|
||||||
if (!isBrowserProject && projectPathData) {
|
if (!isBrowserProject && projectPathData) {
|
||||||
const { project_name, project_path, current_file_name, current_file_path } =
|
const { projectName, projectPath, currentFileName, currentFilePath } =
|
||||||
projectPathData
|
projectPathData
|
||||||
|
|
||||||
const urlObj = new URL(routerData.request.url)
|
const urlObj = new URL(routerData.request.url)
|
||||||
let code = ''
|
let code = ''
|
||||||
|
|
||||||
if (!urlObj.pathname.endsWith('/settings')) {
|
if (!urlObj.pathname.endsWith('/settings')) {
|
||||||
if (!current_file_name || !current_file_path || !project_name) {
|
if (!currentFileName || !currentFilePath || !projectName) {
|
||||||
return redirect(
|
return redirect(
|
||||||
`${PATHS.FILE}/${encodeURIComponent(
|
`${PATHS.FILE}/${encodeURIComponent(
|
||||||
isDesktop()
|
isDesktop()
|
||||||
? (await getProjectInfo(project_path)).default_file
|
? (await getProjectInfo(projectPath)).default_file
|
||||||
: params.id + '/' + PROJECT_ENTRYPOINT
|
: params.id + '/' + PROJECT_ENTRYPOINT
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
code = await window.electron.readFile(current_file_path)
|
code = await window.electron.readFile(currentFilePath)
|
||||||
|
code = normalizeLineEndings(code)
|
||||||
|
|
||||||
// Update both the state and the editor's code.
|
// Update both the state and the editor's code.
|
||||||
// We explicitly do not write to the file here since we are loading from
|
// We explicitly do not write to the file here since we are loading from
|
||||||
// the file system and not the editor.
|
// the file system and not the editor.
|
||||||
codeManager.updateCurrentFilePath(current_file_path)
|
codeManager.updateCurrentFilePath(currentFilePath)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the file system manager to the project path
|
// Set the file system manager to the project path
|
||||||
// So that WASM gets an updated path for operations
|
// So that WASM gets an updated path for operations
|
||||||
fileSystemManager.dir = project_path
|
fileSystemManager.dir = projectPath
|
||||||
|
|
||||||
const defaultProjectData = {
|
const defaultProjectData = {
|
||||||
name: project_name || 'unnamed',
|
name: projectName || 'unnamed',
|
||||||
path: project_path,
|
path: projectPath,
|
||||||
children: [],
|
children: [],
|
||||||
kcl_file_count: 0,
|
kcl_file_count: 0,
|
||||||
directory_count: 0,
|
directory_count: 0,
|
||||||
metadata: null,
|
metadata: null,
|
||||||
default_file: project_path,
|
default_file: projectPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
const maybeProjectInfo = isDesktop()
|
const maybeProjectInfo = isDesktop()
|
||||||
? await getProjectInfo(project_path)
|
? await getProjectInfo(projectPath)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
console.log('maybeProjectInfo', {
|
console.log('maybeProjectInfo', {
|
||||||
@ -137,8 +138,8 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
code,
|
code,
|
||||||
project: maybeProjectInfo ?? defaultProjectData,
|
project: maybeProjectInfo ?? defaultProjectData,
|
||||||
file: {
|
file: {
|
||||||
name: current_file_name || '',
|
name: currentFileName || '',
|
||||||
path: current_file_path || '',
|
path: currentFilePath || '',
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -187,3 +188,7 @@ export const homeLoader: LoaderFunction = async (): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeLineEndings = (str: string, normalized = '\n') => {
|
||||||
|
return str.replace(/\r?\n/g, normalized)
|
||||||
|
}
|
||||||
|
@ -8,8 +8,6 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::settings::types::Configuration;
|
|
||||||
|
|
||||||
/// State management for the application.
|
/// State management for the application.
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -148,99 +146,6 @@ const model = import("{}")"#,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Project route information.
|
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
|
||||||
#[ts(export)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub struct ProjectRoute {
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub project_name: Option<String>,
|
|
||||||
pub project_path: String,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub current_file_name: Option<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub current_file_path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectRoute {
|
|
||||||
/// Get the project state from the url in the route.
|
|
||||||
pub fn from_route(configuration: &Configuration, route: &str) -> Result<Self> {
|
|
||||||
let path = std::path::Path::new(route);
|
|
||||||
// Check if the default project path is in the route.
|
|
||||||
let (project_path, project_name) = if path.starts_with(&configuration.settings.project.directory)
|
|
||||||
&& configuration.settings.project.directory != std::path::PathBuf::default()
|
|
||||||
{
|
|
||||||
// Get the project name.
|
|
||||||
if let Some(project_name) = path
|
|
||||||
.strip_prefix(&configuration.settings.project.directory)?
|
|
||||||
.iter()
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
(
|
|
||||||
configuration
|
|
||||||
.settings
|
|
||||||
.project
|
|
||||||
.directory
|
|
||||||
.join(project_name)
|
|
||||||
.display()
|
|
||||||
.to_string(),
|
|
||||||
Some(project_name.to_string_lossy().to_string()),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(configuration.settings.project.directory.display().to_string(), None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Assume the project path is the parent directory of the file.
|
|
||||||
let project_dir = if path.display().to_string().ends_with(".kcl") {
|
|
||||||
path.parent()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Parent directory not found: {}", path.display()))?
|
|
||||||
} else {
|
|
||||||
path
|
|
||||||
};
|
|
||||||
|
|
||||||
if project_dir == std::path::Path::new("/") {
|
|
||||||
(
|
|
||||||
path.display().to_string(),
|
|
||||||
Some(
|
|
||||||
path.file_name()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("File name not found: {}", path.display()))?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else if let Some(project_name) = project_dir.file_name() {
|
|
||||||
(
|
|
||||||
project_dir.display().to_string(),
|
|
||||||
Some(project_name.to_string_lossy().to_string()),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(project_dir.display().to_string(), None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (current_file_name, current_file_path) = if path.display().to_string() == project_path {
|
|
||||||
(None, None)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
Some(
|
|
||||||
path.file_name()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("File name not found: {}", path.display()))?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
Some(path.display().to_string()),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
project_name,
|
|
||||||
project_path,
|
|
||||||
current_file_name,
|
|
||||||
current_file_path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Information about project.
|
/// Information about project.
|
||||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -535,160 +440,6 @@ impl From<std::fs::Metadata> for FileMetadata {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_std_path() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory =
|
|
||||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
|
||||||
|
|
||||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/main.kcl";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("assembly".to_string()),
|
|
||||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
|
||||||
current_file_name: Some("main.kcl".to_string()),
|
|
||||||
current_file_path: Some(
|
|
||||||
"/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/main.kcl".to_string()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_std_path_dir() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory =
|
|
||||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
|
||||||
|
|
||||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("assembly".to_string()),
|
|
||||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
|
||||||
current_file_name: None,
|
|
||||||
current_file_path: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_std_path_dir_empty() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory =
|
|
||||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
|
||||||
|
|
||||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: None,
|
|
||||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects".to_string(),
|
|
||||||
current_file_name: None,
|
|
||||||
current_file_path: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_outside_std_path() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory =
|
|
||||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
|
||||||
|
|
||||||
let route = "/Users/macinatormax/kittycad/modeling-app/main.kcl";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("modeling-app".to_string()),
|
|
||||||
project_path: "/Users/macinatormax/kittycad/modeling-app".to_string(),
|
|
||||||
current_file_name: Some("main.kcl".to_string()),
|
|
||||||
current_file_path: Some("/Users/macinatormax/kittycad/modeling-app/main.kcl".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_outside_std_path_dir() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory =
|
|
||||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
|
||||||
|
|
||||||
let route = "/Users/macinatormax/kittycad/modeling-app";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("modeling-app".to_string()),
|
|
||||||
project_path: "/Users/macinatormax/kittycad/modeling-app".to_string(),
|
|
||||||
current_file_name: None,
|
|
||||||
current_file_path: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_browser() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory = std::path::PathBuf::default();
|
|
||||||
|
|
||||||
let route = "/browser/main.kcl";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("browser".to_string()),
|
|
||||||
project_path: "/browser".to_string(),
|
|
||||||
current_file_name: Some("main.kcl".to_string()),
|
|
||||||
current_file_path: Some("/browser/main.kcl".to_string())
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_browser_no_path() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory = std::path::PathBuf::default();
|
|
||||||
|
|
||||||
let route = "/browser";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("browser".to_string()),
|
|
||||||
project_path: "/browser".to_string(),
|
|
||||||
current_file_name: None,
|
|
||||||
current_file_path: None
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_project_route_from_route_non_main_file() {
|
|
||||||
let mut configuration = crate::settings::types::Configuration::default();
|
|
||||||
configuration.settings.project.directory =
|
|
||||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
|
||||||
|
|
||||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/thing.kcl";
|
|
||||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
super::ProjectRoute {
|
|
||||||
project_name: Some("assembly".to_string()),
|
|
||||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
|
||||||
current_file_name: Some("thing.kcl".to_string()),
|
|
||||||
current_file_path: Some(
|
|
||||||
"/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/thing.kcl".to_string()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_default_kcl_file_for_dir_non_exist() {
|
async fn test_default_kcl_file_for_dir_non_exist() {
|
||||||
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
@ -566,22 +566,6 @@ pub fn serialize_project_settings(val: JsValue) -> Result<JsValue, String> {
|
|||||||
Ok(JsValue::from_str(&toml_str))
|
Ok(JsValue::from_str(&toml_str))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the project route.
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn parse_project_route(configuration: &str, route: &str) -> Result<JsValue, String> {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
let configuration: kcl_lib::settings::types::Configuration =
|
|
||||||
serde_json::from_str(configuration).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let route =
|
|
||||||
kcl_lib::settings::types::file::ProjectRoute::from_route(&configuration, route).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
|
||||||
// gloo-serialize crate instead.
|
|
||||||
JsValue::from_serde(&route).map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
static ALLOWED_DECODING_FORMATS: &[data_encoding::Encoding] = &[
|
static ALLOWED_DECODING_FORMATS: &[data_encoding::Encoding] = &[
|
||||||
data_encoding::BASE64,
|
data_encoding::BASE64,
|
||||||
data_encoding::BASE64URL,
|
data_encoding::BASE64URL,
|
||||||
|
Reference in New Issue
Block a user