Skip to content

Commit 33a2643

Browse files
committed
test: add integration test for transformer functionality
1 parent 12bb640 commit 33a2643

File tree

4 files changed

+255
-2
lines changed

4 files changed

+255
-2
lines changed

web/libs/editor/tests/e2e/tests/image.transformer.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ for (const shapeName of Object.keys(shapes)) {
127127
shapesTable.add([shapeName]);
128128
}
129129

130-
Data(shapesTable).Scenario(
130+
Data(shapesTable).only.Scenario(
131131
"Check transformer existing for different shapes, their amount and modes.",
132132
async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
133133
const { shapeName } = current;
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { ImageView, Labels, LabelStudio, Sidebar } from "@humansignal/frontend-test/helpers/LSF";
2+
import { Hotkeys } from "@humansignal/frontend-test/helpers/LSF/Hotkeys";
3+
4+
const IMAGE = "https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg";
5+
6+
const annotationEmpty = {
7+
id: "1000",
8+
result: [],
9+
};
10+
11+
const shapes = {
12+
Rectangle: {
13+
drawAction: "drawRect",
14+
hasTransformer: true,
15+
hasRotator: true,
16+
hasMoveToolTransformer: true,
17+
hasMultiSelectionTransformer: true,
18+
hasMultiSelectionRotator: true,
19+
hotKey: "r",
20+
byBBox(x, y, width, height) {
21+
return {
22+
params: [x, y, width, height],
23+
result: { width, height, rotation: 0, x, y },
24+
};
25+
},
26+
},
27+
Ellipse: {
28+
drawAction: "drawRect",
29+
hasTransformer: true,
30+
hasRotator: true,
31+
hasMoveToolTransformer: true,
32+
hasMultiSelectionTransformer: true,
33+
hasMultiSelectionRotator: true,
34+
hotKey: "o",
35+
byBBox(x, y, width, height) {
36+
return {
37+
params: [x + width / 2, y + height / 2, width / 2, height / 2],
38+
result: {
39+
radiusX: width / 2,
40+
radiusY: height / 2,
41+
rotation: 0,
42+
x: x + width / 2,
43+
y: y + height / 2,
44+
},
45+
};
46+
},
47+
},
48+
Polygon: {
49+
drawAction: "drawPolygon",
50+
hasTransformer: false,
51+
hasRotator: false,
52+
hasMoveToolTransformer: true,
53+
hasMultiSelectionTransformer: true,
54+
hasMultiSelectionRotator: false,
55+
hotKey: "p",
56+
byBBox(x, y, width, height) {
57+
const points = [];
58+
59+
points.push([x, y]);
60+
points.push([x + width, y]);
61+
points.push([x + width / 2, y + height / 2]);
62+
points.push([x + width, y + height]);
63+
points.push([x, y + height]);
64+
return {
65+
params: [[...points, points[0]]],
66+
result: {
67+
points,
68+
closed: true,
69+
},
70+
};
71+
},
72+
},
73+
KeyPoint: {
74+
drawAction: "clickAt",
75+
hasTransformer: false,
76+
hasRotator: false,
77+
hasMoveToolTransformer: false,
78+
hasMultiSelectionTransformer: true,
79+
hasMultiSelectionRotator: false,
80+
hotKey: "k",
81+
params: 'strokeWidth="2"',
82+
byBBox(x, y, width, height) {
83+
return {
84+
params: [x + width / 2, y + height / 2],
85+
result: {
86+
x: x + width / 2,
87+
y: y + height / 2,
88+
width: 2,
89+
},
90+
};
91+
},
92+
},
93+
};
94+
95+
const getParamsWithLabels = (shapeName) => ({
96+
config: `
97+
<View>
98+
<Image name="img" value="$image" />
99+
<${shapeName}Labels name="tag" toName="img">
100+
<Label value="${shapeName}" background="orange"/>
101+
</${shapeName}Labels>
102+
</View>`,
103+
data: { image: IMAGE },
104+
});
105+
106+
function drawShapeByBbox(Shape, bbox) {
107+
ImageView[Shape.drawAction](...Shape.byBBox(bbox.x, bbox.y, bbox.width, bbox.height).params);
108+
}
109+
110+
describe("Image Transformer", () => {
111+
for (const shapeName in shapes) {
112+
it(`should exist for different shapes, their amount and modes. -- ${shapeName}`, () => {
113+
const Shape = shapes[shapeName];
114+
const { config, data } = getParamsWithLabels(shapeName);
115+
const bbox1 = {
116+
x: 100,
117+
y: 100,
118+
width: 200,
119+
height: 200,
120+
};
121+
const bbox2 = {
122+
x: 400,
123+
y: 100,
124+
width: 200,
125+
height: 200,
126+
};
127+
const getCenter = (bbox) => [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
128+
129+
LabelStudio.params().config(config).data(data).withResult([]).init();
130+
LabelStudio.waitForObjectsReady();
131+
Sidebar.hasRegions(0);
132+
133+
// Draw two regions
134+
Labels.selectWithHotkey("1");
135+
drawShapeByBbox(Shape, bbox1);
136+
Sidebar.hasRegions(1);
137+
138+
Labels.selectWithHotkey("1");
139+
drawShapeByBbox(Shape, bbox2);
140+
Sidebar.hasRegions(2);
141+
142+
// Check that it wasn't a cause to show a transformer
143+
ImageView.hasNoTransformer();
144+
145+
// Select the first region
146+
ImageView.clickAt(...getCenter(bbox1));
147+
Sidebar.hasSelectedRegions(1);
148+
149+
// Match if transformer exist with expectations in single selected mode
150+
ImageView.shouldHaveTransformer(Shape.hasTransformer);
151+
152+
// Match if rotator at transformer exist with expectations in single selected mode
153+
ImageView.shouldHaveRotater(Shape.hasRotator);
154+
155+
ImageView.selectMoveToolByButton();
156+
// Match if transformer exist with expectations in single selected mode with move tool chosen
157+
ImageView.shouldHaveTransformer(Shape.hasMoveToolTransformer);
158+
159+
// Deselect the previous selected region
160+
Hotkeys.unselect();
161+
162+
// Select 2 regions
163+
ImageView.drawThroughPoints(
164+
[
165+
[bbox1.x - 5, bbox1.y - 5],
166+
[bbox2.x + bbox2.width + 5, bbox2.y + bbox2.height + 5],
167+
],
168+
"steps",
169+
10,
170+
);
171+
});
172+
}
173+
});

web/libs/frontend-test/src/helpers/LSF/Hotkeys.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export const Hotkeys = {
1919
redo() {
2020
pressHotkey("{ctrl}{shift}z", "{command}{shift}z");
2121
},
22+
unselect() {
23+
pressHotkey("u");
24+
},
2225
deleteRegion() {
2326
pressHotkey("{backspace}");
2427
},

web/libs/frontend-test/src/helpers/LSF/ImageView.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ export const ImageView = {
135135
drawingArea
136136
.trigger("mousemove", point[0], point[1], { eventConstructor: "MouseEvent", ...options })
137137
.trigger("mousedown", point[0], point[1], { eventConstructor: "MouseEvent", buttons: 1, ...options })
138-
.trigger("mouseup", point[0], point[1], { eventConstructor: "MouseEvent", buttons: 1, ...options });
138+
.trigger("mouseup", point[0], point[1], { eventConstructor: "MouseEvent", buttons: 1, ...options })
139+
.wait(0.016);
139140
});
140141
},
141142

@@ -153,6 +154,82 @@ export const ImageView = {
153154
this.drawPolygon(realPoints, autoclose, options);
154155
});
155156
},
157+
158+
/**
159+
* Mousedown - mousemove - mouseup drawing through the list of points on the ImageView. Works in couple of lookForStage.
160+
* @example
161+
* await AtImageView.lookForStage();
162+
* AtImageView.drawThroughPoints([[50,50],[200,100],[50,200],[300,300]]);
163+
* @param {number[][]} points - list of pairs of coords
164+
* @param {"steps"|"rate"} mode - mode of firing mousemove event
165+
* @param {number} parameter - parameter for mode
166+
* @param {MouseInteractionOptions} options - options for mousemove event
167+
*/
168+
drawThroughPoints(points: [number, number][], mode = "steps", parameter = 1, options = {}) {
169+
const drawingArea = this.drawingArea.scrollIntoView();
170+
171+
const calcSteps = {
172+
steps: () => parameter,
173+
rate: (p1, p2) => Math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) / parameter,
174+
}[mode];
175+
const startPoint = points[0];
176+
177+
drawingArea
178+
.trigger("mousemove", startPoint[0], startPoint[1], { eventConstructor: "MouseEvent", ...options })
179+
.trigger("mousedown", startPoint[0], startPoint[1], { eventConstructor: "MouseEvent", buttons: 1, ...options });
180+
let i;
181+
for (i = 1; i < points.length; i++) {
182+
const prevPoint = points[i - 1];
183+
const curPoint = points[i];
184+
const steps = calcSteps(prevPoint, curPoint);
185+
186+
for (let k = 1; k <= steps; k++) {
187+
const curX = prevPoint[0] + ((curPoint[0] - prevPoint[0]) * k) / steps;
188+
const curY = prevPoint[1] + ((curPoint[1] - prevPoint[1]) * k) / steps;
189+
drawingArea.trigger("mousemove", curX, curY, { eventConstructor: "MouseEvent", ...options }).wait(0.002);
190+
}
191+
}
192+
drawingArea.trigger("mouseup", points[i - 1][0], points[i - 1][1], {
193+
eventConstructor: "MouseEvent",
194+
buttons: 1,
195+
...options,
196+
});
197+
},
198+
199+
get isTransformerThere() {
200+
return cy.window().then((win) => {
201+
const stage = win.Konva!.stages[0];
202+
const anchors = stage.find("._anchor").filter((shape) => shape.getAttr("visible") !== false);
203+
204+
return !!anchors.length;
205+
});
206+
},
207+
208+
get isRotaterThere() {
209+
return cy.window().then((win) => {
210+
const stage = win.Konva!.stages[0];
211+
const rotaters = stage.find(".rotater").filter((shape) => shape.getAttr("visible") !== false);
212+
213+
return !!rotaters.length;
214+
});
215+
},
216+
217+
hasTransformer() {
218+
this.isTransformerThere.should("be.true");
219+
},
220+
221+
hasNoTransformer() {
222+
this.isTransformerThere.should("be.false");
223+
},
224+
225+
shouldHaveTransformer(should = true) {
226+
this.isTransformerThere.should("be.eq", should);
227+
},
228+
229+
shouldHaveRotater(should = true) {
230+
this.isRotaterThere.should("be.eq", should);
231+
},
232+
156233
/**
157234
* Captures a screenshot of an element to compare later
158235
* @param {string} name name of the screenshot

0 commit comments

Comments
 (0)