Compare commits
35 Commits
achalmers/
...
kcl-0.2.10
Author | SHA1 | Date | |
---|---|---|---|
6e10f75ff6 | |||
03e289af20 | |||
efc140abbf | |||
4dfad19b7e | |||
e56c634b35 | |||
00292abc98 | |||
483d6903d6 | |||
3780996374 | |||
2fde71228a | |||
5cd8ab3812 | |||
9a385fb474 | |||
b740d25bbd | |||
ef350b020b | |||
4d2375faac | |||
22a9f44916 | |||
713a30ed72 | |||
ebed10bc76 | |||
acbe92d717 | |||
e624c9b124 | |||
877eb3ec5e | |||
64500d055a | |||
5df996d877 | |||
84d70751af | |||
3899999465 | |||
9f370fbb56 | |||
f750c4ea8b | |||
e16ecc28a3 | |||
a2d8c5a714 | |||
0bb4586e6d | |||
bbabf04ba6 | |||
37a1208924 | |||
682099c1ad | |||
8f3ad0d43c | |||
be047f5111 | |||
d656a389f8 |
BIN
assets/icon.icns
Normal file
BIN
assets/icon.ico
Normal file
After Width: | Height: | Size: 183 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
BIN
assets/icon@2x.icns
Normal file
38
docs/kcl/cm.md
Normal file
38
docs/kcl/ft.md
Normal file
835
docs/kcl/hollow.md
Normal file
38
docs/kcl/inch.md
Normal file
@ -32,17 +32,21 @@ layout: manual
|
||||
* [`chamfer`](kcl/chamfer)
|
||||
* [`circle`](kcl/circle)
|
||||
* [`close`](kcl/close)
|
||||
* [`cm`](kcl/cm)
|
||||
* [`cos`](kcl/cos)
|
||||
* [`e`](kcl/e)
|
||||
* [`extrude`](kcl/extrude)
|
||||
* [`fillet`](kcl/fillet)
|
||||
* [`floor`](kcl/floor)
|
||||
* [`ft`](kcl/ft)
|
||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||
* [`helix`](kcl/helix)
|
||||
* [`hole`](kcl/hole)
|
||||
* [`hollow`](kcl/hollow)
|
||||
* [`import`](kcl/import)
|
||||
* [`inch`](kcl/inch)
|
||||
* [`int`](kcl/int)
|
||||
* [`lastSegX`](kcl/lastSegX)
|
||||
* [`lastSegY`](kcl/lastSegY)
|
||||
@ -55,8 +59,10 @@ layout: manual
|
||||
* [`log`](kcl/log)
|
||||
* [`log10`](kcl/log10)
|
||||
* [`log2`](kcl/log2)
|
||||
* [`m`](kcl/m)
|
||||
* [`max`](kcl/max)
|
||||
* [`min`](kcl/min)
|
||||
* [`mm`](kcl/mm)
|
||||
* [`patternCircular2d`](kcl/patternCircular2d)
|
||||
* [`patternCircular3d`](kcl/patternCircular3d)
|
||||
* [`patternLinear2d`](kcl/patternLinear2d)
|
||||
@ -89,3 +95,4 @@ layout: manual
|
||||
* [`xLineTo`](kcl/xLineTo)
|
||||
* [`yLine`](kcl/yLine)
|
||||
* [`yLineTo`](kcl/yLineTo)
|
||||
* [`yd`](kcl/yd)
|
||||
|
38
docs/kcl/m.md
Normal file
38
docs/kcl/mm.md
Normal file
5313
docs/kcl/std.json
38
docs/kcl/yd.md
Normal file
@ -6,7 +6,39 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Electron user sidebar menu tests', () => {
|
||||
test.describe('Electron app header tests', () => {
|
||||
test(
|
||||
'Open Command Palette button has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
// No space before the shortcut since it checks textContent.
|
||||
let text
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
text = 'Commands⌘K'
|
||||
break
|
||||
case 'win32':
|
||||
text = 'CommandsCtrl+K'
|
||||
break
|
||||
default: // 'linux' etc.
|
||||
text = 'CommandsCtrl+K'
|
||||
break
|
||||
}
|
||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||
await expect(commandsButton).toBeVisible()
|
||||
await expect(commandsButton).toHaveText(text)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'User settings has correct shortcut',
|
||||
{ tag: '@electron' },
|
@ -1,6 +1,13 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
|
||||
import fsp from 'fs/promises'
|
||||
@ -223,26 +230,24 @@ test(
|
||||
'Opening multiple panes persists when switching projects',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
// Setup multiple projects.
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([
|
||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
||||
fsp.mkdir(routerTemplateDir, { recursive: true }),
|
||||
fsp.mkdir(bracketDir, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/router-template-slate/main.kcl`
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
|
@ -12,50 +12,47 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
})
|
||||
|
||||
test.describe('Command bar tests', () => {
|
||||
// TODO fixme: enter is not working in the command bar
|
||||
test.fixme(
|
||||
'Extrude from command bar selects extrude line after',
|
||||
async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
test('Extrude from command bar selects extrude line after', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> xLine(-20, %)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for xLine.
|
||||
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// TODO fixme: enter is not working in the command bar
|
||||
test.fixme('Fillet from command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for xLine.
|
||||
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Fillet from command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -127,7 +124,7 @@ const extrude001 = extrude(-10, sketch001)`
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
@ -188,7 +185,7 @@ const extrude001 = extrude(-10, sketch001)`
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
@ -253,7 +250,7 @@ const extrude001 = extrude(-10, sketch001)`
|
||||
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await page.keyboard.press('Meta+K')
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
// Search for extrude command and choose it
|
||||
|
@ -332,7 +332,6 @@ test.describe('Copilot ghost text', () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -349,10 +348,10 @@ test.describe('Copilot ghost text', () => {
|
||||
)
|
||||
|
||||
// Going elsewhere in the code should hide the ghost text.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.up('Shift')
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
@ -368,8 +367,6 @@ test.describe('Copilot ghost text', () => {
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
await page.waitForTimeout(800)
|
||||
await u.codeLocator.click()
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
@ -382,17 +379,17 @@ test.describe('Copilot ghost text', () => {
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// Ctrl+z
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
||||
// Ctrl+shift+z
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(`{thing: "blah"}`)
|
||||
@ -411,14 +408,14 @@ test.describe('Copilot ghost text', () => {
|
||||
)
|
||||
|
||||
// Once for the enter.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// Once for the text.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-ghostText').first()).not.toBeVisible()
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setupElectron, tearDown } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
getUtils,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -10,22 +16,19 @@ test(
|
||||
'export works on the first try',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await Promise.all([fsp.mkdir(`${dir}/bracket`, { recursive: true })])
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/bracket/other.kcl`
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(bracketDir, 'other.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
|
@ -16,7 +16,6 @@ test.describe('Editor tests', () => {
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
// check no error to begin with
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
@ -29,9 +28,9 @@ test.describe('Editor tests', () => {
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`)
|
||||
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||
@ -42,9 +41,9 @@ test.describe('Editor tests', () => {
|
||||
// |> close(%)`)
|
||||
|
||||
// uncomment the code
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('/')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XY')
|
||||
@ -148,9 +147,7 @@ test.describe('Editor tests', () => {
|
||||
// Delete all the code.
|
||||
await page.locator('.cm-content').click()
|
||||
// Select all
|
||||
await page.keyboard.press('Control+A')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Meta+A')
|
||||
await page.keyboard.press('ControlOrMeta+A')
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(``)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { setupElectron, tearDown } from './test-utils'
|
||||
import { setupElectron, tearDown, executorInputPath } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
@ -10,17 +11,14 @@ test(
|
||||
'When machine-api server not found butt is disabled and shows the reason',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -58,17 +56,14 @@ test(
|
||||
'When machine-api server not found home screen & project status shows the reason',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import fsp from 'fs/promises'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import {
|
||||
@ -347,17 +354,14 @@ test(
|
||||
'Restarting onboarding on desktop takes one attempt',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/router-template-slate/main.kcl`
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
join(routerTemplateDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import {
|
||||
doExport,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
isOutOfViewInScrollContainer,
|
||||
Paths,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
createProjectAndRenameIt,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'fs'
|
||||
@ -45,17 +47,14 @@ test(
|
||||
'click help/keybindings from project page',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -64,8 +63,6 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
@ -92,17 +89,13 @@ test(
|
||||
'when code with error first loads you get errors in console',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/broken-code`, { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/broken-code-test.kcl',
|
||||
`${dir}/broken-code/main.kcl`
|
||||
executorInputPath('broken-code-test.kcl'),
|
||||
join(dir, 'broken-code', 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -138,17 +131,14 @@ test.describe('Can export from electron app', () => {
|
||||
`Can export using ${method}`,
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -232,10 +222,6 @@ test(
|
||||
'Rename and delete projects, also spam arrow keys when renaming',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
@ -468,7 +454,7 @@ test(
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
test.fixme(
|
||||
test(
|
||||
'File in the file pane should open with a single click',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -524,10 +510,6 @@ test(
|
||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
})
|
||||
@ -535,33 +517,23 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const createProjectAndRenameIt = async (name: string) =>
|
||||
test.step(`Create and rename project ${name}`, async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await page.getByText(`project-000`).hover()
|
||||
await page.getByText(`project-000`).focus()
|
||||
|
||||
await page.getByLabel('sketch').first().click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// type "updated project name"
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type(name)
|
||||
|
||||
await page.getByLabel('checkmark').last().click()
|
||||
const createProjectAndRenameItTest = async ({
|
||||
name,
|
||||
page,
|
||||
}: {
|
||||
name: string
|
||||
page: Page
|
||||
}) => {
|
||||
await test.step(`Create and rename project ${name}`, async () => {
|
||||
await createProjectAndRenameIt({ name, page })
|
||||
})
|
||||
}
|
||||
|
||||
// we need to create the folders so that the order is correct
|
||||
// creating them ahead of time with fs tools means they all have the same timestamp
|
||||
await createProjectAndRenameIt('router-template-slate')
|
||||
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
|
||||
await createProjectAndRenameIt('bracket')
|
||||
await createProjectAndRenameIt('lego')
|
||||
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
|
||||
await createProjectAndRenameItTest({ name: 'bracket', page })
|
||||
await createProjectAndRenameItTest({ name: 'lego', page })
|
||||
|
||||
await test.step('delete the middle project, i.e. the bracket project', async () => {
|
||||
const project = page.getByText('bracket')
|
||||
@ -618,14 +590,52 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Can load a file with CRLF line endings',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||
|
||||
const file = await fsp.readFile(
|
||||
executorInputPath('router-template-slate.kcl'),
|
||||
'utf-8'
|
||||
)
|
||||
// Replace both \r optionally so we don't end up with \r\r\n
|
||||
const fileWithCRLF = file.replace(/\r?\n/g, '\r\n')
|
||||
await fsp.writeFile(
|
||||
join(routerTemplateDir, 'main.kcl'),
|
||||
fileWithCRLF,
|
||||
'utf-8'
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
await page.getByText('router-template-slate').click()
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(u.codeLocator).toContainText('routerDiameter')
|
||||
await expect(u.codeLocator).toContainText('templateGap')
|
||||
await expect(u.codeLocator).toContainText('minClampingDistance')
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Can sort projects on home page',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
})
|
||||
@ -635,33 +645,23 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const createProjectAndRenameIt = async (name: string) =>
|
||||
test.step(`Create and rename project ${name}`, async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await page.getByText(`project-000`).hover()
|
||||
await page.getByText(`project-000`).focus()
|
||||
|
||||
await page.getByLabel('sketch').first().click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// type "updated project name"
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type(name)
|
||||
|
||||
await page.getByLabel('checkmark').last().click()
|
||||
const createProjectAndRenameItTest = async ({
|
||||
name,
|
||||
page,
|
||||
}: {
|
||||
name: string
|
||||
page: Page
|
||||
}) => {
|
||||
await test.step(`Create and rename project ${name}`, async () => {
|
||||
await createProjectAndRenameIt({ name, page })
|
||||
})
|
||||
}
|
||||
|
||||
// we need to create the folders so that the order is correct
|
||||
// creating them ahead of time with fs tools means they all have the same timestamp
|
||||
await createProjectAndRenameIt('router-template-slate')
|
||||
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
|
||||
await createProjectAndRenameIt('bracket')
|
||||
await createProjectAndRenameIt('lego')
|
||||
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
|
||||
await createProjectAndRenameItTest({ name: 'bracket', page })
|
||||
await createProjectAndRenameItTest({ name: 'lego', page })
|
||||
|
||||
await test.step('should be shorted by modified initially', async () => {
|
||||
const lastModifiedButton = page.getByRole('button', {
|
||||
@ -748,10 +748,6 @@ test(
|
||||
'When the project folder is empty, user can create new project and open it.',
|
||||
{ tag: '@electron' },
|
||||
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 u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -836,25 +832,35 @@ test(
|
||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await Promise.all([
|
||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
||||
fsp.mkdir(join(dir, 'router-template-slate'), { recursive: true }),
|
||||
fsp.mkdir(join(dir, 'bracket'), { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/router-template-slate/main.kcl`
|
||||
join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs',
|
||||
'router-template-slate.kcl'
|
||||
),
|
||||
join(dir, 'router-template-slate', 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs',
|
||||
'focusrite_scarlett_mounting_braket.kcl'
|
||||
),
|
||||
join(dir, 'bracket', 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
@ -1053,10 +1059,6 @@ test(
|
||||
'Search projects on desktop home',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const projectData = [
|
||||
['basic bracket', 'focusrite_scarlett_mounting_braket.kcl'],
|
||||
['basic-cube', 'basic_fillet_cube_end.kcl'],
|
||||
@ -1071,7 +1073,7 @@ test(
|
||||
for (const [name, file] of projectData) {
|
||||
await fsp.mkdir(join(dir, name), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', file),
|
||||
executorInputPath(file),
|
||||
join(dir, name, `main.kcl`)
|
||||
)
|
||||
}
|
||||
@ -1118,14 +1120,11 @@ test(
|
||||
'file pane is scrollable when there are many files',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/testProject`, { recursive: true })
|
||||
const testDir = join(dir, 'testProject')
|
||||
await fsp.mkdir(testDir, { recursive: true })
|
||||
const fileNames = [
|
||||
'angled_line.kcl',
|
||||
'basic_fillet_cube_close_opposite.kcl',
|
||||
@ -1189,8 +1188,8 @@ test(
|
||||
]
|
||||
for (const fileName of fileNames) {
|
||||
await fsp.copyFile(
|
||||
`src/wasm-lib/tests/executor/inputs/${fileName}`,
|
||||
`${dir}/testProject/${fileName}`
|
||||
executorInputPath(fileName),
|
||||
join(testDir, fileName)
|
||||
)
|
||||
}
|
||||
},
|
||||
@ -1231,19 +1230,16 @@ test(
|
||||
'select all in code editor does not actually select all, just what is visible (regression)',
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
// src/wasm-lib/tests/executor/inputs/mike_stress_test.kcl
|
||||
const name = 'mike_stress_test'
|
||||
await fsp.mkdir(`${dir}/${name}`, { recursive: true })
|
||||
const testDir = join(dir, name)
|
||||
await fsp.mkdir(testDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
`src/wasm-lib/tests/executor/inputs/${name}.kcl`,
|
||||
`${dir}/${name}/main.kcl`
|
||||
executorInputPath(`${name}.kcl`),
|
||||
join(testDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -1254,18 +1250,13 @@ test(
|
||||
|
||||
await page.getByText('mike_stress_test').click()
|
||||
|
||||
const modifier =
|
||||
process.platform === 'win32' || process.platform === 'linux'
|
||||
? 'Control'
|
||||
: 'Meta'
|
||||
|
||||
await test.step('select all in code editor, check its length', async () => {
|
||||
await u.codeLocator.click()
|
||||
// expect u.codeLocator to have some text
|
||||
await expect(u.codeLocator).toContainText('line(')
|
||||
await page.keyboard.down(modifier)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyA')
|
||||
await page.keyboard.up(modifier)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// check the length of the selected text
|
||||
const selectedText = await page.evaluate(() => {
|
||||
@ -1281,9 +1272,9 @@ test(
|
||||
await test.step('delete all the text, select again and verify there are no characters left', async () => {
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await page.keyboard.down(modifier)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyA')
|
||||
await page.keyboard.up(modifier)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// check the length of the selected text
|
||||
const selectedText = await page.evaluate(() => {
|
||||
@ -1302,10 +1293,6 @@ test(
|
||||
'Settings persist across restarts',
|
||||
{ tag: '@electron' },
|
||||
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 () => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
@ -1350,27 +1337,16 @@ test.describe('Renaming in the file tree', () => {
|
||||
'A file you have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, 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, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
const exampleDir = join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs'
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'cylinder.kcl'),
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
@ -1382,6 +1358,16 @@ test.describe('Renaming in the file tree', () => {
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const newFileName = 'newFileName'
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
@ -1390,7 +1376,6 @@ test.describe('Renaming in the file tree', () => {
|
||||
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const newFileName = 'newFileName'
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
@ -1401,6 +1386,8 @@ test.describe('Renaming in the file tree', () => {
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
await fileToRename.click()
|
||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||
await u.openKclCodePanel()
|
||||
@ -1419,6 +1406,8 @@ test.describe('Renaming in the file tree', () => {
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we navigated', async () => {
|
||||
@ -1442,27 +1431,16 @@ test.describe('Renaming in the file tree', () => {
|
||||
'A file you do not have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, 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, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
const exampleDir = join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs'
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'cylinder.kcl'),
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
@ -1473,6 +1451,14 @@ test.describe('Renaming in the file tree', () => {
|
||||
|
||||
// Constants and locators
|
||||
const newFileName = 'newFileName'
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToRename = page
|
||||
@ -1493,6 +1479,8 @@ test.describe('Renaming in the file tree', () => {
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
@ -1506,6 +1494,8 @@ test.describe('Renaming in the file tree', () => {
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we have not navigated', async () => {
|
||||
@ -1532,30 +1522,19 @@ test.describe('Renaming in the file tree', () => {
|
||||
`A folder you're not inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, 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, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
const exampleDir = join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs'
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'cylinder.kcl'),
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
@ -1573,8 +1552,17 @@ test.describe('Renaming in the file tree', () => {
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('folderToRename')
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
@ -1588,6 +1576,8 @@ test.describe('Renaming in the file tree', () => {
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
@ -1607,6 +1597,8 @@ test.describe('Renaming in the file tree', () => {
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
@ -1617,12 +1609,7 @@ test.describe('Renaming in the file tree', () => {
|
||||
`A folder you are inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const exampleDir = join('src', 'wasm-lib', 'tests', 'executor', 'inputs')
|
||||
const { electronApp, page } = await setupElectron({
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
@ -1630,11 +1617,11 @@ test.describe('Renaming in the file tree', () => {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
join(exampleDir, 'cylinder.kcl'),
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
@ -1655,8 +1642,17 @@ test.describe('Renaming in the file tree', () => {
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('folderToRename')
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and navigate into folder', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
@ -1679,9 +1675,12 @@ test.describe('Renaming in the file tree', () => {
|
||||
expect(newUrl).toContain('folderToRename')
|
||||
expect(newUrl).toContain('someFileWithin.kcl')
|
||||
expect(newUrl).not.toContain('main.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(60000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
@ -1704,9 +1703,124 @@ test.describe('Renaming in the file tree', () => {
|
||||
expect(url).not.toContain('main.kcl')
|
||||
expect(url).toContain(newFolderName)
|
||||
expect(url).toContain('someFileWithin.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Deleting files from the file pane', () => {
|
||||
test(
|
||||
`when main.kcl exists, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const testDir = join(dir, 'testProject')
|
||||
await fsp.mkdir(testDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(testDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(testDir, 'fileToDelete.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('testProject')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToDelete = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToDelete.kcl' }) })
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openFilePanel()
|
||||
|
||||
await fileToDelete.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete fileToDelete.kcl', async () => {
|
||||
await fileToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation', async () => {
|
||||
await u.waitForPageLoad()
|
||||
await expect(fileToDelete).not.toBeVisible()
|
||||
await u.closeFilePanel()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('circle(')
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme('TODO - when main.kcl does not exist', async () => {})
|
||||
})
|
||||
|
||||
test(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const getAllProjects = () => page.getByTestId('project-link').all()
|
||||
page.on('console', console.log)
|
||||
|
||||
await test.step('Should create and name a project called wrist brace', async () => {
|
||||
await createProjectAndRenameIt({ name: 'wrist brace', page })
|
||||
})
|
||||
|
||||
await test.step('Should go through onboarding', async () => {
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await page.getByTestId('user-settings').click()
|
||||
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
||||
|
||||
const numberOfOnboardingSteps = 12
|
||||
for (let clicks = 0; clicks < numberOfOnboardingSteps; clicks++) {
|
||||
await page.getByTestId('onboarding-next').click()
|
||||
}
|
||||
|
||||
await page.getByTestId('project-sidebar-toggle').click()
|
||||
})
|
||||
|
||||
await test.step('Should go home after onboarding is completed', async () => {
|
||||
await page.getByRole('button', { name: 'Go to Home' }).click()
|
||||
})
|
||||
|
||||
await test.step('Should show the original project called wrist brace', async () => {
|
||||
const projectNames = ['Tutorial Project 00', 'wrist brace']
|
||||
for (const [index, projectLink] of (await getAllProjects()).entries()) {
|
||||
await expect(projectLink).toContainText(projectNames[index])
|
||||
}
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import { join } from 'path'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
|
||||
@ -425,17 +432,14 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
`Network health indicator only appears in modeling view`,
|
||||
{ tag: '@electron' },
|
||||
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,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@ -2,13 +2,13 @@ import {
|
||||
expect,
|
||||
Page,
|
||||
Download,
|
||||
TestInfo,
|
||||
BrowserContext,
|
||||
TestInfo,
|
||||
_electron as electron,
|
||||
Locator,
|
||||
test,
|
||||
} from '@playwright/test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import os from 'os'
|
||||
import fsp from 'fs/promises'
|
||||
import fsSync from 'fs'
|
||||
import { join } from 'path'
|
||||
@ -26,6 +26,7 @@ import {
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import { isArray } from 'lib/utils'
|
||||
|
||||
type TestColor = [number, number, number]
|
||||
export const TEST_COLORS = {
|
||||
@ -44,6 +45,9 @@ export const commonPoints = {
|
||||
num2: 14.44,
|
||||
}
|
||||
|
||||
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||
|
||||
async function waitForPageLoadWithRetry(page: Page) {
|
||||
await expect(async () => {
|
||||
await page.goto('/')
|
||||
@ -73,11 +77,10 @@ async function waitForPageLoad(page: Page) {
|
||||
}
|
||||
|
||||
async function removeCurrentCode(page: Page) {
|
||||
const hotkey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
await page.locator('.cm-content').click()
|
||||
await page.keyboard.down(hotkey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('a')
|
||||
await page.keyboard.up(hotkey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-content')).toHaveText('')
|
||||
}
|
||||
@ -205,7 +208,7 @@ export const wiggleMove = async (
|
||||
}
|
||||
|
||||
export const circleMove = async (
|
||||
page: any,
|
||||
page: Page,
|
||||
x: number,
|
||||
y: number,
|
||||
steps: number,
|
||||
@ -311,13 +314,19 @@ export function normaliseKclNumbers(code: string, ignoreZero = true): string {
|
||||
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
|
||||
const browserType = page.context().browser()?.browserType().name()
|
||||
const cdpSession =
|
||||
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||
|
||||
return {
|
||||
const util = {
|
||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||
waitForPageLoad: () => waitForPageLoad(page),
|
||||
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||
@ -484,7 +493,74 @@ export async function getUtils(page: Page) {
|
||||
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>>
|
||||
@ -505,7 +581,7 @@ const _makeTemplate = (
|
||||
templateParts: TemplateStringsArray,
|
||||
...options: TemplateOptions
|
||||
) => {
|
||||
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
|
||||
const length = Math.max(...options.map((a) => (isArray(a) ? a[0] : 0)))
|
||||
let reExpTemplate = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
const currentStr = templateParts.map((str, index) => {
|
||||
@ -513,7 +589,7 @@ const _makeTemplate = (
|
||||
return (
|
||||
escapeRegExp(str) +
|
||||
String(
|
||||
Array.isArray(currentOptions)
|
||||
isArray(currentOptions)
|
||||
? currentOptions[i]
|
||||
: typeof currentOptions === 'number'
|
||||
? currentOptions
|
||||
@ -667,11 +743,6 @@ export const doExport = async (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate modifier key for the platform.
|
||||
*/
|
||||
export const metaModifier = os.platform() === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
export async function tearDown(page: Page, testInfo: TestInfo) {
|
||||
if (testInfo.status === 'skipped') return
|
||||
if (testInfo.status === 'failed') return
|
||||
@ -733,6 +804,7 @@ export async function setup(context: BrowserContext, page: Page) {
|
||||
// kill animations, speeds up tests and reduced flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
|
||||
// Trigger a navigation, since loading file:// doesn't.
|
||||
await page.reload()
|
||||
}
|
||||
|
||||
@ -793,7 +865,7 @@ export async function setupElectron({
|
||||
|
||||
await setup(context, page)
|
||||
|
||||
return { electronApp, page }
|
||||
return { electronApp, page, dir: projectDirName }
|
||||
}
|
||||
|
||||
export async function isOutOfViewInScrollContainer(
|
||||
@ -814,3 +886,33 @@ export async function isOutOfViewInScrollContainer(
|
||||
|
||||
return isOutOfView
|
||||
}
|
||||
|
||||
export async function createProjectAndRenameIt({
|
||||
name,
|
||||
page,
|
||||
}: {
|
||||
name: string
|
||||
page: Page
|
||||
}) {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await page.getByText(`project-000`).hover()
|
||||
await page.getByText(`project-000`).focus()
|
||||
|
||||
await page.getByLabel('sketch').first().click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// type the name passed in
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type(name)
|
||||
|
||||
await page.getByLabel('checkmark').last().click()
|
||||
}
|
||||
|
||||
export function executorInputPath(fileName: string): string {
|
||||
return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName)
|
||||
}
|
||||
|
@ -174,168 +174,166 @@ test.describe('Testing Camera Movement', () => {
|
||||
}, [0, -85, -85])
|
||||
})
|
||||
|
||||
// TODO fixme something is wrong with sketch here
|
||||
test.fixme(
|
||||
'Zoom should be consistent when exiting or entering sketches',
|
||||
async ({ page }) => {
|
||||
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||
// than again for sketching
|
||||
test('Zoom should be consistent when exiting or entering sketches', async ({
|
||||
page,
|
||||
}) => {
|
||||
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||
// than again for sketching
|
||||
|
||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 325)
|
||||
|
||||
let code = `const sketch001 = startSketchOn('XY')`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
// move the camera slightly
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.move(700, 300)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(800, 200)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
let y = 350,
|
||||
x = 948
|
||||
|
||||
await u.canvasLocator.click({ position: { x: 783, y } })
|
||||
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
await u.canvasLocator.click({ position: { x, y } })
|
||||
code += `\n |> line([11.18, 0], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
await u.canvasLocator.click({ position: { x, y: 275 } })
|
||||
code += `\n |> line([0, 6.99], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
// click the line button
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
|
||||
const hoverOverNothing = async () => {
|
||||
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
||||
await page.mouse.move(700, 325)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 325)
|
||||
|
||||
let code = `const sketch001 = startSketchOn('XY')`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
// move the camera slightly
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.move(700, 300)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(800, 200)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
let y = 350,
|
||||
x = 948
|
||||
|
||||
await u.canvasLocator.click({ position: { x: 783, y } })
|
||||
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
await u.canvasLocator.click({ position: { x, y } })
|
||||
code += `\n |> line([11.18, 0], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
await u.canvasLocator.click({ position: { x, y: 275 } })
|
||||
code += `\n |> line([0, 6.99], %)`
|
||||
// await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
// click the line button
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
|
||||
const hoverOverNothing = async () => {
|
||||
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
||||
await page.mouse.move(700, 325)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
// hover over horizontal line
|
||||
await u.canvasLocator.hover({ position: { x: 800, y } })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
// hover over vertical line
|
||||
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// click exit sketch
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
// hover over horizontal line
|
||||
await page.mouse.move(858, y, { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// hover over vertical line
|
||||
await page.mouse.move(x, 325)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// hover over vertical line
|
||||
await page.mouse.move(857, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
// now click it
|
||||
await page.mouse.click(857, y)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await hoverOverNothing()
|
||||
x = 975
|
||||
y = 468
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.move(x, 419, { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.mouse.move(855, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await page.mouse.move(x, 419)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.mouse.move(855, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
// hover over horizontal line
|
||||
await u.canvasLocator.hover({ position: { x: 800, y } })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
// hover over vertical line
|
||||
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// click exit sketch
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
// hover over horizontal line
|
||||
await page.mouse.move(858, y, { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// hover over vertical line
|
||||
await page.mouse.move(x, 325)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
// hover over vertical line
|
||||
await page.mouse.move(857, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
// now click it
|
||||
await page.mouse.click(857, y)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
await page.waitForTimeout(400)
|
||||
|
||||
await hoverOverNothing()
|
||||
x = 975
|
||||
y = 468
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.move(x, 419, { steps: 5 })
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.mouse.move(855, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await hoverOverNothing()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await page.mouse.move(x, 419)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
await hoverOverNothing()
|
||||
|
||||
await page.mouse.move(855, y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||
timeout: 10_000,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
executorInputPath,
|
||||
} from './test-utils'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { TEST_SETTINGS_KEY, TEST_SETTINGS_CORRUPTED } from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
@ -72,7 +79,7 @@ test.describe('Testing settings', () => {
|
||||
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
||||
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('Meta+Shift+,')
|
||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||
|
||||
await expect(headingLocator).toBeVisible()
|
||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||
@ -82,7 +89,7 @@ test.describe('Testing settings', () => {
|
||||
await test.step('Open settings with keyboard shortcut', async () => {
|
||||
await page.getByTestId('settings-close-button').click()
|
||||
await page.locator('.cm-content').click()
|
||||
await page.keyboard.press('Meta+Shift+,')
|
||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||
await expect(headingLocator).toBeVisible()
|
||||
})
|
||||
|
||||
@ -115,6 +122,36 @@ test.describe('Testing settings', () => {
|
||||
).not.toBeChecked()
|
||||
})
|
||||
|
||||
test('Keybindings display the correct hotkey for Command Palette', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await test.step('Open keybindings settings', async () => {
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||
|
||||
// Go to Keybindings tab.
|
||||
const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' })
|
||||
await keybindingsTab.click()
|
||||
})
|
||||
|
||||
// Go to the hotkey for Command Palette.
|
||||
const commandPalette = page.getByText('Toggle Command Palette')
|
||||
await commandPalette.scrollIntoViewIfNeeded()
|
||||
|
||||
// The heading is above it and should be in view now.
|
||||
const commandPaletteHeading = page.getByRole('heading', {
|
||||
name: 'Command Palette',
|
||||
})
|
||||
// The hotkey is in a kbd element next to the heading.
|
||||
const hotkey = commandPaletteHeading.locator('+ div kbd')
|
||||
const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K'
|
||||
await expect(hotkey).toHaveText(text)
|
||||
})
|
||||
|
||||
test('Project and user settings can be reset', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -203,10 +240,11 @@ test.describe('Testing settings', () => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -264,4 +302,69 @@ test.describe('Testing settings', () => {
|
||||
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()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -9,8 +9,6 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
test.describe('Text-to-CAD tests', () => {
|
||||
test('basic lego happy case', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
@ -298,9 +296,9 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(page.locator('textarea')).toContainText(badPrompt)
|
||||
|
||||
// Select all and start a new prompt.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyA')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.type('a 2x4 lego')
|
||||
|
||||
// Submit the new prompt.
|
||||
@ -520,9 +518,9 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
||||
@ -549,13 +547,13 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyA')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||
@ -636,9 +634,9 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
import {
|
||||
doExport,
|
||||
getUtils,
|
||||
makeTemplate,
|
||||
metaModifier,
|
||||
setup,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
@ -17,8 +10,6 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
const CtrlKey = process.platform === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
test('Units menu', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -157,7 +148,7 @@ test('Paste should not work unless an input is focused', async ({
|
||||
|
||||
// Paste without the code pane focused
|
||||
await codeEditorText.blur()
|
||||
await page.keyboard.press(`${metaModifier}+KeyV`)
|
||||
await page.keyboard.press('ControlOrMeta+KeyV')
|
||||
|
||||
// Show that the paste didn't work but typing did
|
||||
await expect(codeEditorText).not.toContainText(pasteContent)
|
||||
@ -166,7 +157,7 @@ test('Paste should not work unless an input is focused', async ({
|
||||
// Paste with the code editor focused
|
||||
// Following this guidance: https://github.com/microsoft/playwright/issues/8114
|
||||
await codeEditorText.focus()
|
||||
await page.keyboard.press(`${metaModifier}+KeyV`)
|
||||
await page.keyboard.press('ControlOrMeta+KeyV')
|
||||
await expect(
|
||||
await page.evaluate(
|
||||
() => document.querySelector('.cm-content')?.textContent
|
||||
@ -380,9 +371,9 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||
await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => {
|
||||
// Since there's code now, we have to get to the end of the line
|
||||
await page.locator('.cm-line').last().click()
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('//')
|
||||
|
@ -6,6 +6,9 @@ import { MakerRpm } from '@electron-forge/maker-rpm'
|
||||
import { VitePlugin } from '@electron-forge/plugin-vite'
|
||||
import { FusesPlugin } from '@electron-forge/plugin-fuses'
|
||||
import { FuseV1Options, FuseVersion } from '@electron/fuses'
|
||||
import path from 'path'
|
||||
|
||||
const rootDir = process.cwd()
|
||||
|
||||
const config: ForgeConfig = {
|
||||
packagerConfig: {
|
||||
@ -19,13 +22,24 @@ const config: ForgeConfig = {
|
||||
}) ||
|
||||
undefined,
|
||||
executableName: 'zoo-modeling-app',
|
||||
icon: path.resolve(rootDir, 'assets', 'icon'),
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
new MakerSquirrel({}),
|
||||
new MakerSquirrel({
|
||||
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||
}),
|
||||
new MakerZIP({}, ['darwin']),
|
||||
new MakerRpm({}),
|
||||
new MakerDeb({}),
|
||||
new MakerRpm({
|
||||
options: {
|
||||
icon: path.resolve(rootDir, 'assets', 'icon.png'),
|
||||
},
|
||||
}),
|
||||
new MakerDeb({
|
||||
options: {
|
||||
icon: path.resolve(rootDir, 'assets', 'icon.png'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
plugins: [
|
||||
new VitePlugin({
|
||||
|
12
package.json
@ -26,7 +26,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@kittycad/lib": "^2.0.0",
|
||||
"@kittycad/lib": "^2.0.1",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@lezer/lr": "^1.4.1",
|
||||
"@react-hook/resize-observer": "^2.0.1",
|
||||
@ -83,7 +83,7 @@
|
||||
"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)",
|
||||
"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",
|
||||
"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",
|
||||
@ -119,7 +119,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@electron-forge/cli": "^7.4.0",
|
||||
"@electron-forge/maker-deb": "^7.4.0",
|
||||
"@electron-forge/maker-rpm": "^7.4.0",
|
||||
@ -138,7 +138,7 @@
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/isomorphic-fetch": "^0.0.39",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^22.4.2",
|
||||
"@types/node": "^22.5.0",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
@ -165,7 +165,7 @@
|
||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||
"happy-dom": "^14.3.10",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^9.0.11",
|
||||
"husky": "^9.1.5",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
@ -176,7 +176,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.4.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
|
@ -17,6 +17,7 @@ export default defineConfig({
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['dot'],
|
||||
['list'],
|
||||
['json', { outputFile: './test-results/report.json' }],
|
||||
['html'],
|
||||
],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineConfig } from '@playwright/test'
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
@ -30,4 +30,24 @@ export default defineConfig({
|
||||
actionTimeout: 15_000,
|
||||
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'
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -190,7 +190,7 @@ function CoreDump() {
|
||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||
[]
|
||||
)
|
||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
||||
useHotkeyWrapper(['mod + shift + .'], () => {
|
||||
toast.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
|
@ -50,6 +50,7 @@ import {
|
||||
ExtrudeGroup,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
sketchGroupFromKclValue,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
engineCommandManager,
|
||||
@ -74,7 +75,13 @@ import {
|
||||
changeSketchArguments,
|
||||
updateStartProfileAtArgs,
|
||||
} from 'lang/std/sketch'
|
||||
import { isOverlap, normaliseAngle, roundOff, throttle } from 'lib/utils'
|
||||
import {
|
||||
isArray,
|
||||
isOverlap,
|
||||
normaliseAngle,
|
||||
roundOff,
|
||||
throttle,
|
||||
} from 'lib/utils'
|
||||
import {
|
||||
addStartProfileAt,
|
||||
createArrayExpression,
|
||||
@ -98,6 +105,7 @@ import {
|
||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -115,6 +123,8 @@ export const SEGMENT_WIDTH_PX = 1.6
|
||||
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
|
||||
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
|
||||
|
||||
type Vec3Array = [number, number, number]
|
||||
|
||||
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
||||
// That mostly mean sketch elements.
|
||||
// Cameras, controls, raycasters, etc are handled by sceneInfra
|
||||
@ -383,7 +393,7 @@ export class SceneEntities {
|
||||
if (err(sketchGroup)) return Promise.reject(sketchGroup)
|
||||
if (!sketchGroup) return Promise.reject('sketchGroup not found')
|
||||
|
||||
if (!Array.isArray(sketchGroup?.value))
|
||||
if (!isArray(sketchGroup?.value))
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
@ -588,10 +598,12 @@ export class SceneEntities {
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
|
||||
const sg = kclManager.programMemory.get(
|
||||
const sg = sketchGroupFromKclValue(
|
||||
kclManager.programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
) as SketchGroup
|
||||
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
|
||||
)
|
||||
if (err(sg)) return sg
|
||||
const lastSeg = sg.value?.slice(-1)[0] || sg.start
|
||||
|
||||
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||
|
||||
@ -786,9 +798,11 @@ export class SceneEntities {
|
||||
programMemoryOverride,
|
||||
})
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketchGroup = programMemory.get(
|
||||
const sketchGroup = sketchGroupFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
) as SketchGroup
|
||||
)
|
||||
if (err(sketchGroup)) return sketchGroup
|
||||
const sgPaths = sketchGroup.value
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
|
||||
@ -840,9 +854,11 @@ export class SceneEntities {
|
||||
|
||||
// Prepare to update the THREEjs scene
|
||||
this.sceneProgramMemory = programMemory
|
||||
const sketchGroup = programMemory.get(
|
||||
const sketchGroup = sketchGroupFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
) as SketchGroup
|
||||
)
|
||||
if (err(sketchGroup)) return sketchGroup
|
||||
const sgPaths = sketchGroup.value
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
|
||||
@ -1111,8 +1127,12 @@ export class SceneEntities {
|
||||
|
||||
const maybeSketchGroup = programMemory.get(variableDeclarationName)
|
||||
let sketchGroup = undefined
|
||||
if (maybeSketchGroup?.type === 'SketchGroup') {
|
||||
sketchGroup = maybeSketchGroup
|
||||
const sg = sketchGroupFromKclValue(
|
||||
maybeSketchGroup,
|
||||
variableDeclarationName
|
||||
)
|
||||
if (!err(sg)) {
|
||||
sketchGroup = sg
|
||||
} else if ((maybeSketchGroup as ExtrudeGroup).sketchGroup) {
|
||||
sketchGroup = (maybeSketchGroup as ExtrudeGroup).sketchGroup
|
||||
}
|
||||
@ -1656,9 +1676,12 @@ function prepareTruncatedMemoryAndAst(
|
||||
)
|
||||
if (err(_node)) return _node
|
||||
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
||||
const lastSeg = (
|
||||
programMemory.get(variableDeclarationName) as SketchGroup
|
||||
).value.slice(-1)[0]
|
||||
const sg = sketchGroupFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sg)) return sg
|
||||
const lastSeg = sg?.value.slice(-1)[0]
|
||||
if (draftSegment) {
|
||||
// truncatedAst needs to setup with another segment at the end
|
||||
let newSegment
|
||||
@ -1782,10 +1805,11 @@ export function sketchGroupFromPathToNode({
|
||||
if (result?.type === 'ExtrudeGroup') {
|
||||
return result.sketchGroup
|
||||
}
|
||||
if (result?.type === 'SketchGroup') {
|
||||
return result
|
||||
const sg = sketchGroupFromKclValue(result, varDec?.id?.name)
|
||||
if (err(sg)) {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
return sg
|
||||
}
|
||||
|
||||
function colorSegment(object: any, color: number) {
|
||||
@ -1823,6 +1847,7 @@ export function getSketchQuaternion(
|
||||
})
|
||||
if (err(sketchGroup)) return sketchGroup
|
||||
const zAxis = sketchGroup?.on.zAxis || sketchNormalBackUp
|
||||
if (!zAxis) return Error('SketchGroup zAxis not found')
|
||||
|
||||
return getQuaternionFromZAxis(massageFormats(zAxis))
|
||||
}
|
||||
@ -1947,8 +1972,6 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
||||
return quaternion
|
||||
}
|
||||
|
||||
function massageFormats(a: any): Vector3 {
|
||||
return Array.isArray(a)
|
||||
? new Vector3(a[0], a[1], a[2])
|
||||
: new Vector3(a.x, a.y, a.z)
|
||||
function massageFormats(a: Vec3Array | Point3d): Vector3 {
|
||||
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
|
||||
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
||||
|
||||
export const CommandBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
@ -24,7 +26,7 @@ export const CommandBar = () => {
|
||||
}, [pathname])
|
||||
|
||||
// Hook up keyboard shortcuts
|
||||
useHotkeyWrapper(['mod+k'], () => {
|
||||
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
||||
if (commandBarState.context.commands.length === 0) return
|
||||
if (commandBarState.matches('Closed')) {
|
||||
commandBarSend({ type: 'Open' })
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Completion } from '@codemirror/autocomplete'
|
||||
import { EditorView, ViewUpdate } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -84,17 +83,13 @@ function CommandBarKclInput({
|
||||
if (event.key === 'Backspace' && value === '') {
|
||||
event.preventDefault()
|
||||
stepBack()
|
||||
} else if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
},
|
||||
}),
|
||||
varMentions(varMentionData),
|
||||
EditorState.transactionFilter.of((tr) => {
|
||||
if (tr.newDoc.lines > 1) {
|
||||
handleSubmit()
|
||||
return []
|
||||
}
|
||||
return tr
|
||||
}),
|
||||
EditorView.updateListener.of((vu: ViewUpdate) => {
|
||||
if (vu.docChanged) {
|
||||
setValue(vu.state.doc.toString())
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
|
||||
|
||||
export function CommandBarOpenButton() {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
@ -12,7 +14,7 @@ export function CommandBarOpenButton() {
|
||||
>
|
||||
<span>Commands</span>
|
||||
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
|
||||
{platform === 'macos' ? '⌘K' : '^/'}
|
||||
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
|
||||
</kbd>
|
||||
</button>
|
||||
)
|
||||
|
@ -61,6 +61,7 @@ function RenameForm({
|
||||
<label>
|
||||
<span className="sr-only">Rename file</span>
|
||||
<input
|
||||
data-testid="file-rename-field"
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
autoFocus
|
||||
@ -395,13 +396,14 @@ export const FileTreeMenu = () => {
|
||||
})
|
||||
}
|
||||
|
||||
useHotkeyWrapper(['meta + n'], createFile)
|
||||
useHotkeyWrapper(['meta + shift + n'], createFolder)
|
||||
useHotkeyWrapper(['mod + n'], createFile)
|
||||
useHotkeyWrapper(['mod + shift + n'], createFolder)
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
data-testid="create-file-button"
|
||||
iconStart={{
|
||||
icon: 'filePlus',
|
||||
iconClassName: '!text-current',
|
||||
@ -417,6 +419,7 @@ export const FileTreeMenu = () => {
|
||||
|
||||
<ActionButton
|
||||
Element="button"
|
||||
data-testid="create-folder-button"
|
||||
iconStart={{
|
||||
icon: 'folderPlus',
|
||||
iconClassName: '!text-current',
|
||||
|
@ -9,7 +9,7 @@ import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import toast from 'react-hot-toast'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
||||
|
||||
export function LowerRightControls({
|
||||
@ -66,6 +66,9 @@ export function LowerRightControls({
|
||||
{children}
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -1,11 +1,16 @@
|
||||
import toast from 'react-hot-toast'
|
||||
import ReactJson from 'react-json-view'
|
||||
import { useMemo } from 'react'
|
||||
import { ProgramMemory, Path, ExtrudeSurface } from 'lang/wasm'
|
||||
import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
ExtrudeSurface,
|
||||
sketchGroupFromKclValue,
|
||||
} from 'lang/wasm'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { trap } from 'lib/trap'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
@ -84,12 +89,13 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
||||
const processedMemory: any = {}
|
||||
for (const [key, val] of programMemory?.visibleEntries()) {
|
||||
if (typeof val.value !== 'function') {
|
||||
if (val.type === 'SketchGroup') {
|
||||
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||
const sg = sketchGroupFromKclValue(val, null)
|
||||
if (val.type === 'ExtrudeGroup') {
|
||||
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||
return rest
|
||||
})
|
||||
} else if (val.type === 'ExtrudeGroup') {
|
||||
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||
} else if (!err(sg)) {
|
||||
processedMemory[key] = sg.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||
return rest
|
||||
})
|
||||
} else if ((val.type as any) === 'Function') {
|
||||
|
@ -1,44 +1,11 @@
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { Platform, platform } from 'lib/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export type Platform = 'macos' | 'windows' | 'linux' | ''
|
||||
|
||||
export default function usePlatform() {
|
||||
const [platformName, setPlatformName] = useState<Platform>('')
|
||||
|
||||
useEffect(() => {
|
||||
function getPlatform(): Platform {
|
||||
const platform = window.electron.platform ?? ''
|
||||
// https://nodejs.org/api/process.html#processplatform
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'macos'
|
||||
case 'win32':
|
||||
return 'windows'
|
||||
// We don't currently care to distinguish between these.
|
||||
case 'android':
|
||||
case 'freebsd':
|
||||
case 'linux':
|
||||
case 'openbsd':
|
||||
case 'sunos':
|
||||
return 'linux'
|
||||
default:
|
||||
console.error('Unknown platform:', platform)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
if (isDesktop()) {
|
||||
setPlatformName(getPlatform())
|
||||
} else {
|
||||
if (navigator.userAgent.indexOf('Mac') !== -1) {
|
||||
setPlatformName('macos')
|
||||
} else if (navigator.userAgent.indexOf('Win') !== -1) {
|
||||
setPlatformName('windows')
|
||||
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
|
||||
setPlatformName('linux')
|
||||
}
|
||||
}
|
||||
setPlatformName(platform())
|
||||
}, [setPlatformName])
|
||||
|
||||
return platformName
|
||||
|
@ -18,41 +18,45 @@ const mySketch001 = startSketchOn('XY')
|
||||
// @ts-ignore
|
||||
const sketch001 = programMemory?.get('mySketch001')
|
||||
expect(sketch001).toEqual({
|
||||
type: 'SketchGroup',
|
||||
on: expect.any(Object),
|
||||
start: {
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [46, 71],
|
||||
},
|
||||
},
|
||||
value: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
tag: null,
|
||||
to: [-1.59, -1.54],
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [77, 102],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [0.46, -5.82],
|
||||
from: [-1.59, -1.54],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
sourceRange: [108, 132],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
],
|
||||
id: expect.any(String),
|
||||
type: 'UserVal',
|
||||
__meta: [{ sourceRange: [46, 71] }],
|
||||
value: {
|
||||
type: 'SketchGroup',
|
||||
on: expect.any(Object),
|
||||
start: {
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [46, 71],
|
||||
},
|
||||
},
|
||||
value: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
tag: null,
|
||||
to: [-1.59, -1.54],
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [77, 102],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [0.46, -5.82],
|
||||
from: [-1.59, -1.54],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
sourceRange: [108, 132],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
],
|
||||
id: expect.any(String),
|
||||
__meta: [{ sourceRange: [46, 71] }],
|
||||
},
|
||||
})
|
||||
})
|
||||
test('extrude artifacts', async () => {
|
||||
|
@ -1,6 +1,12 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
|
||||
import {
|
||||
parse,
|
||||
ProgramMemory,
|
||||
SketchGroup,
|
||||
initPromise,
|
||||
sketchGroupFromKclValue,
|
||||
} from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { KCLError } from './errors'
|
||||
|
||||
@ -52,7 +58,7 @@ const newVar = myVar + 1`
|
||||
`
|
||||
const mem = await exe(code)
|
||||
// geo is three js buffer geometry and is very bloated to have in tests
|
||||
const minusGeo = mem.get('mySketch')?.value
|
||||
const minusGeo = mem.get('mySketch')?.value?.value
|
||||
expect(minusGeo).toEqual([
|
||||
{
|
||||
type: 'ToPoint',
|
||||
@ -146,68 +152,72 @@ const newVar = myVar + 1`
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('mySk1')).toEqual({
|
||||
type: 'SketchGroup',
|
||||
on: expect.any(Object),
|
||||
start: {
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
id: expect.any(String),
|
||||
sourceRange: [39, 63],
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
myPath: {
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [109, 116],
|
||||
},
|
||||
],
|
||||
type: 'TagIdentifier',
|
||||
value: 'myPath',
|
||||
info: expect.any(Object),
|
||||
},
|
||||
},
|
||||
value: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [1, 1],
|
||||
type: 'UserVal',
|
||||
value: {
|
||||
type: 'SketchGroup',
|
||||
on: expect.any(Object),
|
||||
start: {
|
||||
to: [0, 0],
|
||||
from: [0, 0],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
sourceRange: [69, 85],
|
||||
id: expect.any(String),
|
||||
sourceRange: [39, 63],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [0, 1],
|
||||
from: [1, 1],
|
||||
__geoMeta: {
|
||||
sourceRange: [91, 117],
|
||||
id: expect.any(String),
|
||||
},
|
||||
tag: {
|
||||
end: 116,
|
||||
start: 109,
|
||||
type: 'TagDeclarator',
|
||||
tags: {
|
||||
myPath: {
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [109, 116],
|
||||
},
|
||||
],
|
||||
type: 'TagIdentifier',
|
||||
value: 'myPath',
|
||||
digest: null,
|
||||
info: expect.any(Object),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [1, 1],
|
||||
from: [0, 1],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
sourceRange: [123, 139],
|
||||
id: expect.any(String),
|
||||
value: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [1, 1],
|
||||
from: [0, 0],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
sourceRange: [69, 85],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
id: expect.any(String),
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [0, 1],
|
||||
from: [1, 1],
|
||||
__geoMeta: {
|
||||
sourceRange: [91, 117],
|
||||
id: expect.any(String),
|
||||
},
|
||||
tag: {
|
||||
end: 116,
|
||||
start: 109,
|
||||
type: 'TagDeclarator',
|
||||
value: 'myPath',
|
||||
digest: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [1, 1],
|
||||
from: [0, 1],
|
||||
tag: null,
|
||||
__geoMeta: {
|
||||
sourceRange: [123, 139],
|
||||
id: expect.any(String),
|
||||
},
|
||||
},
|
||||
],
|
||||
id: expect.any(String),
|
||||
__meta: [{ sourceRange: [39, 63] }],
|
||||
},
|
||||
__meta: [{ sourceRange: [39, 63] }],
|
||||
})
|
||||
})
|
||||
@ -358,7 +368,7 @@ describe('testing math operators', () => {
|
||||
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
const sketch = mem.get('part001')
|
||||
const sketch = sketchGroupFromKclValue(mem.get('part001'), 'part001')
|
||||
// result of `-legLen(5, min(3, 999))` should be -4
|
||||
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
|
||||
expect(yVal).toBe(-4)
|
||||
@ -376,7 +386,7 @@ describe('testing math operators', () => {
|
||||
``,
|
||||
].join('\n')
|
||||
const mem = await exe(code)
|
||||
const sketch = mem.get('part001')
|
||||
const sketch = sketchGroupFromKclValue(mem.get('part001'), 'part001')
|
||||
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
||||
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
|
||||
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
|
||||
@ -385,7 +395,10 @@ describe('testing math operators', () => {
|
||||
`legLen(segLen(seg01), myVar)`
|
||||
)
|
||||
const removedUnaryExpMem = await exe(removedUnaryExp)
|
||||
const removedUnaryExpMemSketch = removedUnaryExpMem.get('part001')
|
||||
const removedUnaryExpMemSketch = sketchGroupFromKclValue(
|
||||
removedUnaryExpMem.get('part001'),
|
||||
'part001'
|
||||
)
|
||||
|
||||
// without the minus sign, the y value should be 8
|
||||
expect((removedUnaryExpMemSketch as SketchGroup).value?.[1]?.to).toEqual([
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
PathToNode,
|
||||
ProgramMemory,
|
||||
SourceRange,
|
||||
SketchGroup,
|
||||
sketchGroupFromKclValue,
|
||||
} from './wasm'
|
||||
import {
|
||||
isNodeSafeToReplacePath,
|
||||
@ -563,7 +563,7 @@ export function createArrayExpression(
|
||||
start: 0,
|
||||
end: 0,
|
||||
digest: null,
|
||||
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
elements,
|
||||
}
|
||||
}
|
||||
@ -577,7 +577,7 @@ export function createPipeExpression(
|
||||
end: 0,
|
||||
digest: null,
|
||||
body,
|
||||
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,6 +613,7 @@ export function createObjectExpression(properties: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
digest: null,
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
properties: Object.entries(properties).map(([key, value]) => ({
|
||||
type: 'ObjectProperty',
|
||||
start: 0,
|
||||
@ -981,7 +982,11 @@ export async function deleteFromSelection(
|
||||
if (err(parent)) {
|
||||
return
|
||||
}
|
||||
const sketchToPreserve = programMemory.get(sketchName) as SketchGroup
|
||||
const sketchToPreserve = sketchGroupFromKclValue(
|
||||
programMemory.get(sketchName),
|
||||
sketchName
|
||||
)
|
||||
if (err(sketchToPreserve)) return sketchToPreserve
|
||||
console.log('sketchName', sketchName)
|
||||
// Can't kick off multiple requests at once as getFaceDetails
|
||||
// is three engine calls in one and they conflict
|
||||
@ -1061,3 +1066,7 @@ export async function deleteFromSelection(
|
||||
|
||||
return new Error('Selection not recognised, could not delete')
|
||||
}
|
||||
|
||||
const nonCodeMetaEmpty = () => {
|
||||
return { nonCodeNodes: {}, start: [], digest: null }
|
||||
}
|
||||
|
@ -10,12 +10,12 @@ import {
|
||||
Program,
|
||||
ProgramMemory,
|
||||
ReturnStatement,
|
||||
SketchGroup,
|
||||
SourceRange,
|
||||
SyntaxType,
|
||||
Expr,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
sketchGroupFromKclValue,
|
||||
} from './wasm'
|
||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||
@ -661,15 +661,16 @@ export function isLinesParallelAndConstrained(
|
||||
if (err(_varDec)) return _varDec
|
||||
const varDec = _varDec.node
|
||||
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
|
||||
const path = programMemory?.get(varName) as SketchGroup
|
||||
const sg = sketchGroupFromKclValue(programMemory?.get(varName), varName)
|
||||
if (err(sg)) return sg
|
||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||
path,
|
||||
sg,
|
||||
primaryLine.range
|
||||
)
|
||||
if (err(_primarySegment)) return _primarySegment
|
||||
const primarySegment = _primarySegment.segment
|
||||
|
||||
const _segment = getSketchSegmentFromSourceRange(path, secondaryLine.range)
|
||||
const _segment = getSketchSegmentFromSourceRange(sg, secondaryLine.range)
|
||||
if (err(_segment)) return _segment
|
||||
const { segment: secondarySegment, index: secondaryIndex } = _segment
|
||||
const primaryAngle = getAngle(primarySegment.from, primarySegment.to)
|
||||
@ -708,9 +709,7 @@ export function isLinesParallelAndConstrained(
|
||||
constraintType === 'angle' || constraintLevel === 'full'
|
||||
|
||||
// get the previous segment
|
||||
const prevSegment = (programMemory.get(varName) as SketchGroup).value[
|
||||
secondaryIndex - 1
|
||||
]
|
||||
const prevSegment = sg.value[secondaryIndex - 1]
|
||||
const prevSourceRange = prevSegment.__geoMeta.sourceRange
|
||||
|
||||
const isParallelAndConstrained =
|
||||
@ -779,7 +778,10 @@ export function hasExtrudeSketchGroup({
|
||||
if (varDec.type !== 'VariableDeclaration') return false
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
||||
return (
|
||||
varValue?.type === 'ExtrudeGroup' ||
|
||||
!err(sketchGroupFromKclValue(varValue, varName))
|
||||
)
|
||||
}
|
||||
|
||||
export function isSingleCursorInPipe(
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
Literal,
|
||||
VariableDeclaration,
|
||||
Identifier,
|
||||
sketchGroupFromKclValue,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
@ -1009,9 +1010,12 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
const { node: varDec } = nodeMeta2
|
||||
|
||||
const variableName = varDec.id.name
|
||||
const sketch = previousProgramMemory?.get(variableName)
|
||||
if (!sketch || sketch.type !== 'SketchGroup') {
|
||||
return new Error('not a SketchGroup')
|
||||
const sketch = sketchGroupFromKclValue(
|
||||
previousProgramMemory?.get(variableName),
|
||||
variableName
|
||||
)
|
||||
if (err(sketch)) {
|
||||
return sketch
|
||||
}
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
||||
@ -1105,10 +1109,11 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
const { node: varDec } = nodeMeta2
|
||||
const variableName = varDec.id.name
|
||||
const sketch = previousProgramMemory?.get(variableName)
|
||||
if (!sketch || sketch.type !== 'SketchGroup') {
|
||||
return new Error('not a SketchGroup')
|
||||
}
|
||||
const sketch = sketchGroupFromKclValue(
|
||||
previousProgramMemory?.get(variableName),
|
||||
variableName
|
||||
)
|
||||
if (err(sketch)) return sketch
|
||||
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
||||
@ -1443,7 +1448,11 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
|
||||
const { node: varDec } = nodeMeta2
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const sketchGroup = previousProgramMemory.get(varName) as SketchGroup
|
||||
const sketchGroup = sketchGroupFromKclValue(
|
||||
previousProgramMemory.get(varName),
|
||||
varName
|
||||
)
|
||||
if (err(sketchGroup)) return sketchGroup
|
||||
const intersectPath = sketchGroup.value.find(
|
||||
({ tag }: Path) => tag && tag.value === intersectTagName
|
||||
)
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { parse, SketchGroup, recast, initPromise } from '../wasm'
|
||||
import {
|
||||
parse,
|
||||
SketchGroup,
|
||||
recast,
|
||||
initPromise,
|
||||
sketchGroupFromKclValue,
|
||||
} from '../wasm'
|
||||
import {
|
||||
ConstraintType,
|
||||
getTransformInfos,
|
||||
@ -362,10 +368,11 @@ const part001 = startSketchOn('XY')
|
||||
it('normal case works', async () => {
|
||||
const programMemory = await enginelessExecutor(parse(code))
|
||||
const index = code.indexOf('// normal-segment') - 7
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
programMemory.get('part001') as SketchGroup,
|
||||
[index, index]
|
||||
)
|
||||
const sg = sketchGroupFromKclValue(
|
||||
programMemory.get('part001'),
|
||||
'part001'
|
||||
) as SketchGroup
|
||||
const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
expect(segment).toEqual({
|
||||
@ -379,7 +386,10 @@ const part001 = startSketchOn('XY')
|
||||
const programMemory = await enginelessExecutor(parse(code))
|
||||
const index = code.indexOf('// segment-in-start') - 7
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
programMemory.get('part001') as SketchGroup,
|
||||
sketchGroupFromKclValue(
|
||||
programMemory.get('part001'),
|
||||
'part001'
|
||||
) as SketchGroup,
|
||||
[index, index]
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
VariableDeclarator,
|
||||
PathToNode,
|
||||
ProgramMemory,
|
||||
sketchGroupFromKclValue,
|
||||
} from '../wasm'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
@ -1636,12 +1637,16 @@ export function transformAstSketchLines({
|
||||
})
|
||||
|
||||
const varName = varDec.node.id.name
|
||||
let sketchGroup = programMemory.get(varName)
|
||||
if (sketchGroup?.type === 'ExtrudeGroup') {
|
||||
sketchGroup = sketchGroup.sketchGroup
|
||||
let kclVal = programMemory.get(varName)
|
||||
let sketchGroup
|
||||
if (kclVal?.type === 'ExtrudeGroup') {
|
||||
sketchGroup = kclVal.sketchGroup
|
||||
} else {
|
||||
sketchGroup = sketchGroupFromKclValue(kclVal, varName)
|
||||
if (err(sketchGroup)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
||||
return new Error('not a sketch group')
|
||||
const segMeta = getSketchSegmentFromPathToNode(
|
||||
sketchGroup,
|
||||
ast,
|
||||
|
@ -16,7 +16,6 @@ import init, {
|
||||
parse_app_settings,
|
||||
parse_project_settings,
|
||||
default_project_settings,
|
||||
parse_project_route,
|
||||
base64_decode,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
@ -33,11 +32,11 @@ import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { TEST } from 'env'
|
||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||
import { err } from 'lib/trap'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||
import { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
||||
@ -126,6 +125,7 @@ export const parse = (code: string | Error): Program | Error => {
|
||||
const program: Program = parse_wasm(code)
|
||||
return program
|
||||
} catch (e: any) {
|
||||
// throw e
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
return new KCLError(
|
||||
parsed.kind,
|
||||
@ -312,7 +312,7 @@ export class ProgramMemory {
|
||||
*/
|
||||
hasSketchOrExtrudeGroup(): boolean {
|
||||
for (const node of this.visibleEntries().values()) {
|
||||
if (node.type === 'ExtrudeGroup' || node.type === 'SketchGroup') {
|
||||
if (node.type === 'ExtrudeGroup' || node.value?.type === 'SketchGroup') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -332,6 +332,28 @@ export class ProgramMemory {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: In the future, make the parameter be a KclValue.
|
||||
export function sketchGroupFromKclValue(
|
||||
obj: any,
|
||||
varName: string | null
|
||||
): SketchGroup | Error {
|
||||
if (obj?.value?.type === 'SketchGroup') return obj.value
|
||||
if (obj?.value?.type === 'ExtrudeGroup') return obj.value.sketchGroup
|
||||
if (obj?.type === 'ExtrudeGroup') return obj.sketchGroup
|
||||
if (!varName) {
|
||||
varName = 'a KCL value'
|
||||
}
|
||||
const actualType = obj?.value?.type ?? obj?.type
|
||||
if (actualType) {
|
||||
console.log(obj)
|
||||
return new Error(
|
||||
`Expected ${varName} to be a sketchGroup or extrudeGroup, but it was ${actualType} instead.`
|
||||
)
|
||||
} else {
|
||||
return new Error(`Expected ${varName} to be a sketchGroup, but it wasn't.`)
|
||||
}
|
||||
}
|
||||
|
||||
export const executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
@ -590,13 +612,6 @@ export function parseProjectSettings(
|
||||
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 {
|
||||
try {
|
||||
const decoded = base64_decode(base64)
|
||||
|
@ -109,11 +109,13 @@ export class CoreDumpManager {
|
||||
|
||||
getWebrtcStats(): Promise<string> {
|
||||
if (!this.engineCommandManager.engineConnection) {
|
||||
throw new Error('Engine connection not initialized')
|
||||
// when the engine connection is not available, return an empty object.
|
||||
return Promise.resolve(JSON.stringify({}))
|
||||
}
|
||||
|
||||
if (!this.engineCommandManager.engineConnection.webrtcStatsCollector) {
|
||||
throw new Error('Engine webrtcStatsCollector not initialized')
|
||||
// when the engine connection is not available, return an empty object.
|
||||
return Promise.resolve(JSON.stringify({}))
|
||||
}
|
||||
|
||||
return this.engineCommandManager.engineConnection
|
||||
|
@ -19,7 +19,6 @@ import {
|
||||
import { DeepPartial } from './types'
|
||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
export { parseProjectRoute } from 'lang/wasm'
|
||||
|
||||
export async function renameProjectDirectory(
|
||||
projectPath: string,
|
||||
@ -39,7 +38,7 @@ export async function renameProjectDirectory(
|
||||
|
||||
// Make sure the new name does not exist.
|
||||
const newPath = window.electron.path.join(
|
||||
projectPath.split('/').slice(0, -1).join('/'),
|
||||
window.electron.path.dirname(projectPath),
|
||||
newName
|
||||
)
|
||||
try {
|
||||
@ -186,9 +185,9 @@ const collectAllFilesRecursiveFrom = async (path: string) => {
|
||||
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 = {
|
||||
name: pathParts.slice(-1)[0],
|
||||
name: name,
|
||||
path,
|
||||
children: [],
|
||||
}
|
||||
@ -330,7 +329,6 @@ export async function getProjectInfo(projectPath: string): Promise<Project> {
|
||||
new Error(`Project path is not a directory: ${projectPath}`)
|
||||
)
|
||||
}
|
||||
|
||||
let walked = await collectAllFilesRecursiveFrom(projectPath)
|
||||
let default_file = await getDefaultKclFileForDir(projectPath, walked)
|
||||
const metadata = await window.electron.stat(projectPath)
|
||||
@ -443,28 +441,34 @@ export const readProjectSettingsFile = async (
|
||||
return configObj
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the app settings file, or creates an initial one if it doesn't exist.
|
||||
*/
|
||||
export const readAppSettingsFile = async () => {
|
||||
let settingsPath = await getAppSettingsFilePath()
|
||||
try {
|
||||
await window.electron.stat(settingsPath)
|
||||
} catch (e) {
|
||||
if (e === 'ENOENT') {
|
||||
const config = defaultAppSettings()
|
||||
if (err(config)) return Promise.reject(config)
|
||||
if (!config.settings?.app)
|
||||
return Promise.reject(new Error('config.app is falsey'))
|
||||
|
||||
config.settings.app.project_directory = await getInitialDefaultDir()
|
||||
return config
|
||||
// The file exists, read it and parse it.
|
||||
if (window.electron.exists(settingsPath)) {
|
||||
const configToml = await window.electron.readFile(settingsPath)
|
||||
const configObj = parseAppSettings(configToml)
|
||||
if (err(configObj)) {
|
||||
return Promise.reject(configObj)
|
||||
}
|
||||
}
|
||||
const configToml = await window.electron.readFile(settingsPath)
|
||||
const configObj = parseAppSettings(configToml)
|
||||
if (err(configObj)) {
|
||||
return Promise.reject(configObj)
|
||||
|
||||
return configObj
|
||||
}
|
||||
|
||||
return configObj
|
||||
// The file doesn't exist, create a new one.
|
||||
// This defaultAppConfig is truly an empty object every time.
|
||||
const defaultAppConfig = defaultAppSettings()
|
||||
if (err(defaultAppConfig)) {
|
||||
return Promise.reject(defaultAppConfig)
|
||||
}
|
||||
const initialDirConfig: DeepPartial<Configuration> = {
|
||||
settings: { project: { directory: await getInitialDefaultDir() } },
|
||||
}
|
||||
const config = Object.assign(defaultAppConfig, initialDirConfig)
|
||||
return config
|
||||
}
|
||||
|
||||
export const writeAppSettingsFile = async (tomlStr: string) => {
|
||||
|