diff --git a/e2e/helpers/index.ts b/e2e/helpers/index.ts index dd15b414..f2e60f94 100644 --- a/e2e/helpers/index.ts +++ b/e2e/helpers/index.ts @@ -1,2 +1,3 @@ export * from './konva-testing.helpers'; export * from './position.helpers'; +export * from './properties.helpers'; diff --git a/e2e/helpers/position.helpers.ts b/e2e/helpers/position.helpers.ts index c5964c90..7e519880 100644 --- a/e2e/helpers/position.helpers.ts +++ b/e2e/helpers/position.helpers.ts @@ -5,6 +5,11 @@ export interface Position { y: number; } +export interface ComponentWithCategory { + name: string; + category?: string; +} + export const getLocatorPosition = async ( locator: Locator ): Promise => { @@ -44,6 +49,18 @@ export const dragAndDrop = async ( await page.mouse.up(); }; +const getTargetPosition = ( + canvasPosition: { x: number; y: number }, + displacementQty: number, + multiplyFactor: number +): Position => { + const positionDisplacement = displacementQty * (multiplyFactor + 1); + return { + x: canvasPosition.x + displacementQty + positionDisplacement, + y: canvasPosition.y + positionDisplacement, + }; +}; + export const addComponentsToCanvas = async ( page: Page, components: string[], @@ -58,18 +75,83 @@ export const addComponentsToCanvas = async ( await component.scrollIntoViewIfNeeded(); const position = await getLocatorPosition(component); - const targetPosition = ( - displacementQty: number, - multiplyFactor: number - ) => { - const positionDisplacement = displacementQty * (multiplyFactor + 1); - return { - x: canvasPosition.x + displacementQty + positionDisplacement, - y: canvasPosition.y + positionDisplacement, - }; - }; - - await dragAndDrop(page, position, targetPosition(displacementQty, index)); + const targetPosition = getTargetPosition( + canvasPosition, + displacementQty, + index + ); + await dragAndDrop(page, position, targetPosition); + } +}; + +export const addComponentsWithDifferentCategoriesToCanvas = async ( + page: Page, + components: ComponentWithCategory[], + displacementQty: number = 120 +) => { + // Handle empty array + if (components.length === 0) { + return; + } + + const stageCanvas = await page.locator('#konva-stage canvas').nth(1); + const canvasPosition = await stageCanvas.boundingBox(); + if (!canvasPosition) throw new Error('No canvas found'); + + let currentCategory: string | undefined = undefined; + + for await (const [index, componentConfig] of components.entries()) { + try { + // Change category only if it's different from current one + if ( + componentConfig.category && + componentConfig.category !== currentCategory + ) { + const categoryButton = page.getByText(componentConfig.category, { + exact: true, + }); + + // Check if category exists before clicking + await categoryButton.waitFor({ state: 'visible', timeout: 3000 }); + await categoryButton.click(); + + // Wait a bit for the category change to take effect + await page.waitForTimeout(500); + currentCategory = componentConfig.category; + } + + // Find component with better handling for duplicates + let component = page.getByAltText(componentConfig.name, { exact: true }); + + // Check if there are multiple elements with the same alt text + const componentCount = await component.count(); + + if (componentCount > 1) { + // Handle duplicates by selecting the first visible one in the current category context + console.warn( + `Multiple components found with name "${componentConfig.name}". Using first visible one.` + ); + component = component.first(); + } + + // Wait for component to be available + await component.waitFor({ state: 'visible', timeout: 5000 }); + await component.scrollIntoViewIfNeeded(); + const position = await getLocatorPosition(component); + + const targetPosition = getTargetPosition( + canvasPosition, + displacementQty, + index + ); + await dragAndDrop(page, position, targetPosition); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + throw new Error( + `Failed to add component "${componentConfig.name}" from category "${componentConfig.category || 'default'}": ${errorMessage}` + ); + } } }; @@ -77,6 +159,26 @@ export const getShapePosition = async (shape: Group): Promise => { return { x: shape?.attrs.x, y: shape?.attrs.y }; }; +export const selectAllComponentsInCanvas = async ( + page: Page, + selectionArea?: { start: Position; end: Position } +) => { + // Clear any existing selection first + await page.mouse.click(800, 130); + + // Small delay to ensure the click is processed + await page.waitForTimeout(100); + + const selectionStart = selectionArea?.start || { x: 260, y: 130 }; + const selectionEnd = selectionArea?.end || { x: 1000, y: 650 }; + + // Perform drag selection using the proven coordinates + await dragAndDrop(page, selectionStart, selectionEnd); + + // Small delay to ensure selection is processed + await page.waitForTimeout(200); +}; + export const moveSelected = ( page: Page, direction: string, diff --git a/e2e/helpers/properties.helpers.ts b/e2e/helpers/properties.helpers.ts new file mode 100644 index 00000000..4c750d6b --- /dev/null +++ b/e2e/helpers/properties.helpers.ts @@ -0,0 +1,21 @@ +import { Page, expect } from '@playwright/test'; +import { getByShapeType } from './konva-testing.helpers'; +import { Group } from 'konva/lib/Group'; + +export const getShapeBackgroundColor = async ( + page: Page, + shapeType: string +): Promise => { + const shape = (await getByShapeType(page, shapeType)) as Group; + return shape?.children?.[0]?.attrs?.fill; +}; + +export const checkPropertiesExist = async ( + page: Page, + properties: string[] +) => { + for (const property of properties) { + const propLocator = page.getByText(property, { exact: true }); + await expect(propLocator).toBeVisible(); + } +}; diff --git a/e2e/props/multi-select-bg-and-common-props.spec.ts b/e2e/props/multi-select-bg-and-common-props.spec.ts new file mode 100644 index 00000000..5dd6c766 --- /dev/null +++ b/e2e/props/multi-select-bg-and-common-props.spec.ts @@ -0,0 +1,79 @@ +import { test, expect } from '@playwright/test'; +import { + getTransformer, + ComponentWithCategory, + getShapeBackgroundColor, + addComponentsWithDifferentCategoriesToCanvas, + selectAllComponentsInCanvas, + checkPropertiesExist, +} from '../helpers'; + +test('when selecting a button and a rectangle, select both, change background color to red, both should update their bg to red', async ({ + page, +}) => { + await page.goto(''); + + // Add components to canvas + const components: ComponentWithCategory[] = [ + { name: 'Button' }, // Button is in default 'Components' category + { name: 'Rectangle', category: 'Basic Shapes' }, + ]; + await addComponentsWithDifferentCategoriesToCanvas(page, components); + + // Select all components in canvas + await selectAllComponentsInCanvas(page); + + // Confirm both items are selected + const selectedItems = await getTransformer(page); + expect(selectedItems._nodes.length).toEqual(2); + + // Change background color to red + const bgSelector = page + .getByText('Background') + .locator('..') + .locator('button'); + await bgSelector.click(); + + const redColorBox = page.locator( + 'div[style*="background-color: rgb(221, 0, 0)"]' + ); + await redColorBox.click(); + + // Verify that both items have red background + const buttonBgColor = await getShapeBackgroundColor(page, 'button'); + const rectangleBgColor = await getShapeBackgroundColor(page, 'rectangle'); + + expect(buttonBgColor).toBe('#DD0000'); + expect(rectangleBgColor).toBe('#DD0000'); +}); + +test('verify that in the props we can find the common props of both items', async ({ + page, +}) => { + await page.goto(''); + + // Add components to canvas + const components: ComponentWithCategory[] = [ + { name: 'Button' }, + { name: 'Rectangle', category: 'Basic Shapes' }, + ]; + await addComponentsWithDifferentCategoriesToCanvas(page, components); + + // Select all components in canvas + await selectAllComponentsInCanvas(page); + + // Confirm both items are selected + const selectedItems = await getTransformer(page); + expect(selectedItems._nodes.length).toEqual(2); + + const commonProps: string[] = [ + 'Layering', + 'Stroke', + 'Stroke style', + 'Background', + 'Border-radius', + ]; + + // Verify common properties are visible in the properties panel + await checkPropertiesExist(page, commonProps); +});