From bd16902f022fc09f1b7b1c1bfa09978e9a709eca Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Fri, 31 May 2024 11:36:08 +1000 Subject: [PATCH] fix single selection angle constraint (#2555) * fix single selection angle constraint * fix angle for multi selections * make test more robust for makos --- e2e/playwright/flow-tests.spec.ts | 180 ++++++++++++++++++++ e2e/playwright/test-utils.ts | 13 ++ src/clientSideScene/ClientSideSceneComp.tsx | 3 +- src/components/ActionButtonDropdown.tsx | 1 + src/components/AvailableVarsHelpers.tsx | 6 +- src/components/ModelingMachineProvider.tsx | 39 ++++- src/components/Toolbar/SetAngleBetween.tsx | 10 +- src/components/Toolbar/setAngleLength.tsx | 4 + src/lang/util.ts | 16 ++ src/machines/modelingMachine.ts | 1 + 10 files changed, 262 insertions(+), 11 deletions(-) diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts index 941e35383..370c331d4 100644 --- a/e2e/playwright/flow-tests.spec.ts +++ b/e2e/playwright/flow-tests.spec.ts @@ -2440,6 +2440,186 @@ test('Extrude from command bar selects extrude line after', async ({ }) test.describe('Testing constraints', () => { + test.describe('Test Angle constraint double segment selection', () => { + const cases = [ + { + testName: 'Add variable', + addVariable: true, + axisSelect: false, + value: "segAng('seg01', %) + angle001", + }, + { + testName: 'No variable', + addVariable: false, + axisSelect: false, + value: "segAng('seg01', %) + 22.69", + }, + { + testName: 'Add variable, selecting axis', + addVariable: true, + axisSelect: true, + value: 'QUARTER_TURN - angle001', + }, + { + testName: 'No variable, selecting axis', + addVariable: false, + axisSelect: true, + value: 'QUARTER_TURN - 7', + }, + ] as const + for (const { testName, addVariable, value, axisSelect } of cases) { + test(`${testName}`, async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const yo = 5 +const part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %) + |> line([78.92, -120.11], %) + |> line([9.16, 77.79], %) + |> line([41.19, 28.97], %) +const part002 = startSketchOn('XZ') + |> startProfileAt([299.05, 231.45], %) + |> xLine(-425.34, %, 'seg-what') + |> yLine(-264.06, %) + |> xLine(segLen('seg-what', %), %) + |> lineTo([profileStartX(%), profileStartY(%)], %)` + ) + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + await page.goto('/') + await u.waitForAuthSkipAppStart() + + await page.getByText('line([74.36, 130.4], %)').click() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + + const [line1, line3] = await Promise.all([ + u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`), + u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`), + ]) + + if (axisSelect) { + await page.mouse.click(600, 130) + } else { + await page.mouse.click(line1.x, line1.y) + } + await page.keyboard.down('Shift') + await page.mouse.click(line3.x, line3.y) + await page.waitForTimeout(100) // this wait is needed for webkit - not sure why + await page.keyboard.up('Shift') + await page + .getByRole('button', { + name: 'Constrain', + }) + .click() + await page.getByTestId('angle').click() + + const createNewVariableCheckbox = page.getByTestId( + 'create-new-variable-checkbox' + ) + const isChecked = await createNewVariableCheckbox.isChecked() + ;((isChecked && !addVariable) || (!isChecked && addVariable)) && + (await createNewVariableCheckbox.click()) + + await page + .getByRole('button', { name: 'Add constraining value' }) + .click() + + // checking activeLines assures the cursors are where they should be + const codeAfter = [ + "|> line([74.36, 130.4], %, 'seg01')", + `|> angledLine([${value}, 78.33], %)`, + ] + if (axisSelect) codeAfter.shift() + + const activeLinesContent = await page.locator('.cm-activeLine').all() + await Promise.all( + activeLinesContent.map(async (line, i) => { + await expect(page.locator('.cm-content')).toContainText( + codeAfter[i] + ) + // if the code is an active line then the cursor should be on that line + await expect(line).toHaveText(codeAfter[i]) + }) + ) + + // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state + await expect(page.getByTestId('segment-overlay')).toHaveCount(4) + }) + } + }) + test.describe('Test Angle constraint single selection', () => { + const cases = [ + { + testName: 'Add variable', + addVariable: true, + value: 'angle001', + }, + { + testName: 'No variable', + addVariable: false, + value: '83', + }, + ] as const + for (const { testName, addVariable, value } of cases) { + test(`${testName}`, async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const yo = 5 + const part001 = startSketchOn('XZ') + |> startProfileAt([-7.54, -26.74], %) + |> line([74.36, 130.4], %) + |> line([78.92, -120.11], %) + |> line([9.16, 77.79], %) + |> line([41.19, 28.97], %) + const part002 = startSketchOn('XZ') + |> startProfileAt([299.05, 231.45], %) + |> xLine(-425.34, %, 'seg-what') + |> yLine(-264.06, %) + |> xLine(segLen('seg-what', %), %) + |> lineTo([profileStartX(%), profileStartY(%)], %)` + ) + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + await page.goto('/') + await u.waitForAuthSkipAppStart() + + await page.getByText('line([74.36, 130.4], %)').click() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + + const line3 = await u.getSegmentBodyCoords( + `[data-overlay-index="${2}"]` + ) + + await page.mouse.click(line3.x, line3.y) + await page + .getByRole('button', { + name: 'Constrain', + }) + .click() + await page.getByTestId('angle').click() + + if (!addVariable) { + await page.getByTestId('create-new-variable-checkbox').click() + } + await page + .getByRole('button', { name: 'Add constraining value' }) + .click() + + const changedCode = `|> angledLine([${value}, 78.33], %)` + await expect(page.locator('.cm-content')).toContainText(changedCode) + // checking active assures the cursor is where it should be + await expect(page.locator('.cm-activeLine')).toHaveText(changedCode) + + // checking the count of the overlays is a good proxy check that the client sketch scene is in a good state + await expect(page.getByTestId('segment-overlay')).toHaveCount(4) + }) + } + }) test.describe('Many segments - no modal constraints', () => { const cases = [ { diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 578502426..52348e14e 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -132,6 +132,19 @@ export async function getUtils(page: Page) { }, waitForCmdReceive: (commandType: string) => waitForCmdReceive(page, commandType), + getSegmentBodyCoords: async (locator: string, px = 30) => { + const overlay = page.locator(locator) + const bbox = await overlay + .boundingBox() + .then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })) + const angle = Number(await overlay.getAttribute('data-overlay-angle')) + const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px + const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px + return { + x: bbox.x + angleXOffset, + y: bbox.y - angleYOffset, + } + }, getBoundingBox: async (locator: string) => page .locator(locator) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index 1d38dc0f8..7f4e9b3f3 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -531,8 +531,7 @@ const ConstraintSymbol = ({ varNameMap[_type as LineInputsType]?.implicitConstraintDesc const node = useMemo( - () => - getNodeFromPath(parse(recast(kclManager.ast)), pathToNode).node, + () => getNodeFromPath(kclManager.ast, pathToNode).node, [kclManager.ast, pathToNode] ) const range: SourceRange = node ? [node.start, node.end] : [0, 0] diff --git a/src/components/ActionButtonDropdown.tsx b/src/components/ActionButtonDropdown.tsx index 75ddb6722..d8b5790b1 100644 --- a/src/components/ActionButtonDropdown.tsx +++ b/src/components/ActionButtonDropdown.tsx @@ -39,6 +39,7 @@ export function ActionButtonDropdown({ onClick={item.onClick} className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60" disabled={item.disabled} + data-testid={item.label} > {item.label} {item.shortcut && ( diff --git a/src/components/AvailableVarsHelpers.tsx b/src/components/AvailableVarsHelpers.tsx index fa081fc06..635c6e306 100644 --- a/src/components/AvailableVarsHelpers.tsx +++ b/src/components/AvailableVarsHelpers.tsx @@ -214,13 +214,17 @@ export const CreateNewVariable = ({ }) => { return ( <> -