Files
modeling-app/e2e/playwright/feature-tree-pane.spec.ts
Frank Noirot 9008fb636f Add edit flows for extrude and offset plane operations (#5045)
* Start implementing a "prepareToEdit" callback for extrude

* Start of generic edit flow for operations

* Actually invoking command bar send generically on double-click

* Refactor: break out non-React hook helper to calculate Kcl expression value

* Add unit tests, fmt

* Integrate helper to get calculated KclExpression

* Clean up unused imports, simplify use of `programMemoryFromVariables`

* Implement basic extrude editing

* Refactor: move DefaultPlanesStr to its own lib file

* Add support for editing offset planes

* Add Edit right-click menu option

* Turn off edit flow for sketch for now

* Add e2e tests for sketch and offset plane editing, fix bug found with offset plane editing

* Add failing e2e extrude edit test

* Remove action version of extrude AST mod

* Fix behavior when adding a constant while editing operation, fixing e2e test

* Patch in changes from 61b02b5703

* Remove shell's prepareToEdit

* Add other Surface types to `artifactIsPlaneWithPaths`

* refactor: rename `item` to `operation`

* Allow `prepareToEdit` to fail with a toast, signal sketch-on-offset is unimplemented

* Rework sketch e2e test to test several working and failing cases

* Fix tsc errors related to making `codeRef` optional

* Make basic error messages more friendly

* fmt

* Reset modifyAst.ts to main

* Fix broken artifactGraph unit test

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Remove unused import

* Look at this (photo)Graph *in the voice of Nickelback*

* Make the offset plane insert at the end, not one before

* Fix bug caught by e2e test failure with "Command needs review" logic

* Update src/machines/modelingMachine.ts

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Remove console logs per @pierremtb

* Update src/components/CommandBar/CommandBarHeader.tsx

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* Use better programMemory init thanks @jtran

* Fix tsc post merge of #5068

* Fix logic for `artifactIsPlaneWithPaths` post-merge

* Need to disable the sketch-on-face case now that artifactGraph is in Rust. Will active in a future PR (cc @jtran)

* Re-run CI after snapshots

* Update FeatureTreePane to not use `useCommandsContext`, missed during merge

* Fix merge issue, import location change on edited file

* fix click test step, which I believe is waiting for context scripts to load

* Convert toolbarFixture.exeIndicator to getter

We need to convert all these selectors on fixtures to getters, because
they can go stale if called on the fixture constructor.

* Missed a dumb little thing in toolbarFixture.ts

* Fix goof with merge

* fmt

* Another dumb missed thing during merge

I gotta get used to the LazyGit merge tool I'm not good at it yet

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Conver sceneFixture's exeIndicator to a getter

Locators on fixtures will be frozen from the time of the fixture's
initialization, I'm increasingly convinced

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Post-kwargs E2E test cleanup

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-05 19:33:40 -05:00

415 lines
13 KiB
TypeScript

import { test, expect } from './zoo-test'
import * as fsp from 'fs/promises'
import { join } from 'path'
const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) {
return 5 * x
}
export fn triangle() {
return startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(10, %)
|> line(end = [-10, -5])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
}
length001 = timesFive(1) * 5
sketch001 = startSketchOn('XZ')
|> startProfileAt([20, 10], %)
|> line(end = [10, 10])
|> angledLine([-45, length001], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
revolve001 = revolve({ axis = "X" }, sketch001)
triangle()
|> extrude(length = 30)
plane001 = offsetPlane('XY', 10)
sketch002 = startSketchOn(plane001)
|> startProfileAt([-20, 0], %)
|> line(end = [5, -15])
|> xLine(-10, %)
|> line(endAbsolute = [-40, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch002, length = 10)
`
const FEAUTRE_TREE_SKETCH_CODE = `sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> angledLine([0, 4], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
2
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%)
extrude001 = extrude(sketch001, length = 10)
sketch002 = startSketchOn(extrude001, rectangleSegmentB001)
|> circle({
center = [-1, 2],
radius = .5
}, %)
plane001 = offsetPlane('XZ', -5)
sketch003 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 5 }, %)
`
test.describe('Feature Tree pane', () => {
test(
'User can go to definition and go to function definition',
{ tag: '@electron' },
async ({ context, homePage, scene, editor, toolbar }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.writeFile(
join(bracketDir, 'main.kcl'),
FEATURE_TREE_EXAMPLE_CODE,
'utf-8'
)
})
await test.step('setup test', async () => {
await homePage.expectState({
projectCards: [
{
title: 'test-sample',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject('test-sample')
await scene.waitForExecutionDone()
await editor.closePane()
await toolbar.openFeatureTreePane()
})
async function testViewSource({
operationName,
operationIndex,
expectedActiveLine,
}: {
operationName: string
operationIndex: number
expectedActiveLine: string
}) {
await test.step(`Go to definition of the ${operationName}`, async () => {
await toolbar.viewSourceOnOperation(operationName, operationIndex)
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: [expectedActiveLine],
})
await expect(
editor.activeLine.first(),
`${operationName} code should be scrolled into view`
).toBeVisible()
})
}
await testViewSource({
operationName: 'Offset Plane',
operationIndex: 0,
expectedActiveLine: "plane001 = offsetPlane('XY', 10)",
})
await testViewSource({
operationName: 'Extrude',
operationIndex: 1,
expectedActiveLine: 'extrude001 = extrude(sketch002, length = 10)',
})
await testViewSource({
operationName: 'Revolve',
operationIndex: 0,
expectedActiveLine: 'revolve001 = revolve({ axis = "X" }, sketch001)',
})
await testViewSource({
operationName: 'Triangle',
operationIndex: 0,
expectedActiveLine: 'triangle()',
})
await test.step('Go to definition on the triangle function', async () => {
await toolbar.goToDefinitionOnOperation('Triangle', 0)
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: ['export fn triangle() {'],
})
await expect(
editor.activeLine.first(),
'Triangle function definition should be scrolled into view'
).toBeVisible()
})
}
)
test(
`User can edit sketch (but not on offset plane yet) from the feature tree`,
{ tag: '@electron' },
async ({ context, homePage, scene, editor, toolbar, page }) => {
const unavailableToastMessage = page.getByText(
'Editing sketches on faces or offset planes through the feature tree is not yet supported'
)
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.writeFile(
join(bracketDir, 'main.kcl'),
FEAUTRE_TREE_SKETCH_CODE,
'utf-8'
)
})
await test.step('setup test', async () => {
await homePage.expectState({
projectCards: [
{
title: 'test-sample',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject('test-sample')
await scene.waitForExecutionDone()
await toolbar.openFeatureTreePane()
})
await test.step('On a default plane should work', async () => {
await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
await expect(
toolbar.exitSketchBtn,
'We should be in sketch mode now'
).toBeVisible()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: ["sketch001 = startSketchOn('XZ')"],
})
await toolbar.exitSketchBtn.click()
})
await test.step('On an extrude face should *not* work', async () => {
// Tooltip is getting in the way of clicking, so I'm first closing the pane
await toolbar.closeFeatureTreePane()
await (await toolbar.getFeatureTreeOperation('Sketch', 1)).dblclick()
await expect(
unavailableToastMessage,
'We should see a toast message about this'
).toBeVisible()
await unavailableToastMessage.waitFor({ state: 'detached' })
// TODO - turn on once we update the artifactGraph in Rust
// to include the proper source location for the extrude face
// await expect(
// toolbar.exitSketchBtn,
// 'We should be in sketch mode now'
// ).toBeVisible()
// await editor.expectState({
// highlightedCode: '',
// diagnostics: [],
// activeLines: ['|>circle({center=[-1,2],radius=.5},%)'],
// })
// await toolbar.exitSketchBtn.click()
})
await test.step('On an offset plane should *not* work', async () => {
// Tooltip is getting in the way of clicking, so I'm first closing the pane
await toolbar.closeFeatureTreePane()
await (await toolbar.getFeatureTreeOperation('Sketch', 2)).dblclick()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: ['|>circle({center=[0,0],radius=5},%)'],
})
await expect(
toolbar.exitSketchBtn,
'We should not be in sketch mode now'
).not.toBeVisible()
await expect(
page.getByText(
'Editing sketches on faces or offset planes through the feature tree is not yet supported'
),
'We should see a toast message about this'
).toBeVisible()
})
}
)
test(`User can edit an extrude operation from the feature tree`, async ({
context,
homePage,
scene,
editor,
toolbar,
cmdBar,
page,
}) => {
const initialInput = '23'
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 5 }, %)
renamedExtrude = extrude(sketch001, length = ${initialInput})`
const newConstantName = 'distance001'
const expectedCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 5 }, %)
${newConstantName} = 23
renamedExtrude = extrude(sketch001, length = ${newConstantName})`
await context.folderSetupFn(async (dir) => {
const testDir = join(dir, 'test-sample')
await fsp.mkdir(testDir, { recursive: true })
await fsp.writeFile(join(testDir, 'main.kcl'), initialCode, 'utf-8')
})
await test.step('setup test', async () => {
await homePage.expectState({
projectCards: [
{
title: 'test-sample',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject('test-sample')
await scene.waitForExecutionDone()
await toolbar.openFeatureTreePane()
})
await test.step('Double click on the extrude operation', async () => {
await (await toolbar.getFeatureTreeOperation('Extrude', 0))
.first()
.dblclick()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: [
`renamedExtrude = extrude(sketch001, length = ${initialInput})`,
],
})
await cmdBar.expectState({
commandName: 'Extrude',
stage: 'arguments',
currentArgKey: 'distance',
currentArgValue: initialInput,
headerArguments: {
Selection: '1 face',
Distance: initialInput,
},
highlightedHeaderArg: 'distance',
})
})
await test.step('Add a named constant for distance argument and submit', async () => {
await expect(cmdBar.currentArgumentInput).toBeVisible()
const addVariableButton = page.getByRole('button', {
name: 'Create new variable',
})
await addVariableButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 face',
// The calculated value is shown in the argument summary
Distance: initialInput,
},
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: [
`renamedExtrude = extrude(sketch001, length = ${newConstantName})`,
],
})
await editor.expectEditor.toContain(expectedCode, {
shouldNormalise: true,
})
})
})
test(`User can edit an offset plane operation from the feature tree`, async ({
context,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const testCode = (value: string) => `p = offsetPlane('XY', ${value})`
const initialInput = '10'
const initialCode = testCode(initialInput)
const newInput = '5 + 10'
const expectedCode = testCode(newInput)
await context.folderSetupFn(async (dir) => {
const testDir = join(dir, 'test-sample')
await fsp.mkdir(testDir, { recursive: true })
await fsp.writeFile(join(testDir, 'main.kcl'), initialCode, 'utf-8')
})
await test.step('setup test', async () => {
await homePage.expectState({
projectCards: [
{
title: 'test-sample',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject('test-sample')
await scene.waitForExecutionDone()
await toolbar.openFeatureTreePane()
})
await test.step('Double click on the offset plane operation', async () => {
await (await toolbar.getFeatureTreeOperation('Offset Plane', 0))
.first()
.dblclick()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: [initialCode],
})
await cmdBar.expectState({
commandName: 'Offset plane',
stage: 'arguments',
currentArgKey: 'distance',
currentArgValue: initialInput,
headerArguments: {
Plane: '1 plane',
Distance: initialInput,
},
highlightedHeaderArg: 'distance',
})
})
await test.step('Edit the distance argument and submit', async () => {
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Plane: '1 plane',
// We show the calculated value in the argument summary
Distance: '15',
},
commandName: 'Offset plane',
})
await cmdBar.progressCmdBar()
await editor.expectState({
highlightedCode: '',
diagnostics: [],
activeLines: [expectedCode],
})
})
})
})