Skip to content

Commit f4bd853

Browse files
authored
Merge pull request #770 from Lemoncode/feature/#606-multiple-selection-background-prop-update-and-common-props
Feature/#606 multiple selection background prop update and common props
2 parents 408d72c + 565061b commit f4bd853

File tree

4 files changed

+215
-12
lines changed

4 files changed

+215
-12
lines changed

e2e/helpers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './konva-testing.helpers';
22
export * from './position.helpers';
3+
export * from './properties.helpers';

e2e/helpers/position.helpers.ts

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ export interface Position {
55
y: number;
66
}
77

8+
export interface ComponentWithCategory {
9+
name: string;
10+
category?: string;
11+
}
12+
813
export const getLocatorPosition = async (
914
locator: Locator
1015
): Promise<Position> => {
@@ -44,6 +49,18 @@ export const dragAndDrop = async (
4449
await page.mouse.up();
4550
};
4651

52+
const getTargetPosition = (
53+
canvasPosition: { x: number; y: number },
54+
displacementQty: number,
55+
multiplyFactor: number
56+
): Position => {
57+
const positionDisplacement = displacementQty * (multiplyFactor + 1);
58+
return {
59+
x: canvasPosition.x + displacementQty + positionDisplacement,
60+
y: canvasPosition.y + positionDisplacement,
61+
};
62+
};
63+
4764
export const addComponentsToCanvas = async (
4865
page: Page,
4966
components: string[],
@@ -58,25 +75,110 @@ export const addComponentsToCanvas = async (
5875
await component.scrollIntoViewIfNeeded();
5976
const position = await getLocatorPosition(component);
6077

61-
const targetPosition = (
62-
displacementQty: number,
63-
multiplyFactor: number
64-
) => {
65-
const positionDisplacement = displacementQty * (multiplyFactor + 1);
66-
return {
67-
x: canvasPosition.x + displacementQty + positionDisplacement,
68-
y: canvasPosition.y + positionDisplacement,
69-
};
70-
};
71-
72-
await dragAndDrop(page, position, targetPosition(displacementQty, index));
78+
const targetPosition = getTargetPosition(
79+
canvasPosition,
80+
displacementQty,
81+
index
82+
);
83+
await dragAndDrop(page, position, targetPosition);
84+
}
85+
};
86+
87+
export const addComponentsWithDifferentCategoriesToCanvas = async (
88+
page: Page,
89+
components: ComponentWithCategory[],
90+
displacementQty: number = 120
91+
) => {
92+
// Handle empty array
93+
if (components.length === 0) {
94+
return;
95+
}
96+
97+
const stageCanvas = await page.locator('#konva-stage canvas').nth(1);
98+
const canvasPosition = await stageCanvas.boundingBox();
99+
if (!canvasPosition) throw new Error('No canvas found');
100+
101+
let currentCategory: string | undefined = undefined;
102+
103+
for await (const [index, componentConfig] of components.entries()) {
104+
try {
105+
// Change category only if it's different from current one
106+
if (
107+
componentConfig.category &&
108+
componentConfig.category !== currentCategory
109+
) {
110+
const categoryButton = page.getByText(componentConfig.category, {
111+
exact: true,
112+
});
113+
114+
// Check if category exists before clicking
115+
await categoryButton.waitFor({ state: 'visible', timeout: 3000 });
116+
await categoryButton.click();
117+
118+
// Wait a bit for the category change to take effect
119+
await page.waitForTimeout(500);
120+
currentCategory = componentConfig.category;
121+
}
122+
123+
// Find component with better handling for duplicates
124+
let component = page.getByAltText(componentConfig.name, { exact: true });
125+
126+
// Check if there are multiple elements with the same alt text
127+
const componentCount = await component.count();
128+
129+
if (componentCount > 1) {
130+
// Handle duplicates by selecting the first visible one in the current category context
131+
console.warn(
132+
`Multiple components found with name "${componentConfig.name}". Using first visible one.`
133+
);
134+
component = component.first();
135+
}
136+
137+
// Wait for component to be available
138+
await component.waitFor({ state: 'visible', timeout: 5000 });
139+
await component.scrollIntoViewIfNeeded();
140+
const position = await getLocatorPosition(component);
141+
142+
const targetPosition = getTargetPosition(
143+
canvasPosition,
144+
displacementQty,
145+
index
146+
);
147+
await dragAndDrop(page, position, targetPosition);
148+
} catch (error) {
149+
const errorMessage =
150+
error instanceof Error ? error.message : String(error);
151+
throw new Error(
152+
`Failed to add component "${componentConfig.name}" from category "${componentConfig.category || 'default'}": ${errorMessage}`
153+
);
154+
}
73155
}
74156
};
75157

76158
export const getShapePosition = async (shape: Group): Promise<Position> => {
77159
return { x: shape?.attrs.x, y: shape?.attrs.y };
78160
};
79161

162+
export const selectAllComponentsInCanvas = async (
163+
page: Page,
164+
selectionArea?: { start: Position; end: Position }
165+
) => {
166+
// Clear any existing selection first
167+
await page.mouse.click(800, 130);
168+
169+
// Small delay to ensure the click is processed
170+
await page.waitForTimeout(100);
171+
172+
const selectionStart = selectionArea?.start || { x: 260, y: 130 };
173+
const selectionEnd = selectionArea?.end || { x: 1000, y: 650 };
174+
175+
// Perform drag selection using the proven coordinates
176+
await dragAndDrop(page, selectionStart, selectionEnd);
177+
178+
// Small delay to ensure selection is processed
179+
await page.waitForTimeout(200);
180+
};
181+
80182
export const moveSelected = (
81183
page: Page,
82184
direction: string,

e2e/helpers/properties.helpers.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Page, expect } from '@playwright/test';
2+
import { getByShapeType } from './konva-testing.helpers';
3+
import { Group } from 'konva/lib/Group';
4+
5+
export const getShapeBackgroundColor = async (
6+
page: Page,
7+
shapeType: string
8+
): Promise<string> => {
9+
const shape = (await getByShapeType(page, shapeType)) as Group;
10+
return shape?.children?.[0]?.attrs?.fill;
11+
};
12+
13+
export const checkPropertiesExist = async (
14+
page: Page,
15+
properties: string[]
16+
) => {
17+
for (const property of properties) {
18+
const propLocator = page.getByText(property, { exact: true });
19+
await expect(propLocator).toBeVisible();
20+
}
21+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { test, expect } from '@playwright/test';
2+
import {
3+
getTransformer,
4+
ComponentWithCategory,
5+
getShapeBackgroundColor,
6+
addComponentsWithDifferentCategoriesToCanvas,
7+
selectAllComponentsInCanvas,
8+
checkPropertiesExist,
9+
} from '../helpers';
10+
11+
test('when selecting a button and a rectangle, select both, change background color to red, both should update their bg to red', async ({
12+
page,
13+
}) => {
14+
await page.goto('');
15+
16+
// Add components to canvas
17+
const components: ComponentWithCategory[] = [
18+
{ name: 'Button' }, // Button is in default 'Components' category
19+
{ name: 'Rectangle', category: 'Basic Shapes' },
20+
];
21+
await addComponentsWithDifferentCategoriesToCanvas(page, components);
22+
23+
// Select all components in canvas
24+
await selectAllComponentsInCanvas(page);
25+
26+
// Confirm both items are selected
27+
const selectedItems = await getTransformer(page);
28+
expect(selectedItems._nodes.length).toEqual(2);
29+
30+
// Change background color to red
31+
const bgSelector = page
32+
.getByText('Background')
33+
.locator('..')
34+
.locator('button');
35+
await bgSelector.click();
36+
37+
const redColorBox = page.locator(
38+
'div[style*="background-color: rgb(221, 0, 0)"]'
39+
);
40+
await redColorBox.click();
41+
42+
// Verify that both items have red background
43+
const buttonBgColor = await getShapeBackgroundColor(page, 'button');
44+
const rectangleBgColor = await getShapeBackgroundColor(page, 'rectangle');
45+
46+
expect(buttonBgColor).toBe('#DD0000');
47+
expect(rectangleBgColor).toBe('#DD0000');
48+
});
49+
50+
test('verify that in the props we can find the common props of both items', async ({
51+
page,
52+
}) => {
53+
await page.goto('');
54+
55+
// Add components to canvas
56+
const components: ComponentWithCategory[] = [
57+
{ name: 'Button' },
58+
{ name: 'Rectangle', category: 'Basic Shapes' },
59+
];
60+
await addComponentsWithDifferentCategoriesToCanvas(page, components);
61+
62+
// Select all components in canvas
63+
await selectAllComponentsInCanvas(page);
64+
65+
// Confirm both items are selected
66+
const selectedItems = await getTransformer(page);
67+
expect(selectedItems._nodes.length).toEqual(2);
68+
69+
const commonProps: string[] = [
70+
'Layering',
71+
'Stroke',
72+
'Stroke style',
73+
'Background',
74+
'Border-radius',
75+
];
76+
77+
// Verify common properties are visible in the properties panel
78+
await checkPropertiesExist(page, commonProps);
79+
});

0 commit comments

Comments
 (0)