Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 155 additions & 1 deletion e2e/tests/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,73 @@ const initLabelStudio = async ({ config, data, completions = [{ result: [] }], p
done();
};

/**
* Wait for the main Image object to be loaded
* @param {function} done codecept async success handler
*/
const waitForImage = async done => {
const img = document.querySelector("[alt=LS]");
if (!img || img.complete) return done();
img.onload = done;
};

/**
* Float numbers can't be compared strictly, so convert any numbers or structures with numbers
* to same structures but with rounded numbers (int for ints, fixed(2) for floats)
* @param {*} data
*/
const convertToFixed = data => {
if (["string", "number"].includes(typeof data)) {
const n = Number(data);
return Number.isInteger(n) ? n : +Number(n).toFixed(2);
}
if (Array.isArray(data)) {
return data.map(n => convertToFixed(n));
}
if (typeof data === "object") {
const result = {};
for (let key in data) {
result[key] = convertToFixed(data[key]);
}
return result;
}
return data;
};

/**
* Create convertor for any measures to relative form on image with given dimensions
* Accepts numbers, arrays ([x, y] treated as a special coords array) and hash objects
* With [706, 882] given as image sizes:
* assert.equal(convertToImageSize(123), 17.42);
* assert.deepEqual(convertToImageSize([123, 123]), [17.42, 13.95]);
* assert.deepEqual(
* convertToImageSize({ width: 123, height: 123, radiusY: 123, coords: [123, 123] }),
* { width: 17.42, height: 13.95, radiusY: 13.95, coords: [17.42, 13.95] }
* );
* @param {number} width
* @param {number} height
*/
const getSizeConvertor = (width, height) =>
function convert(data, size = width) {
if (typeof data === "number") return convertToFixed((data * 100) / size);
if (Array.isArray(data)) {
if (data.length === 2) return [convert(data[0]), convert(data[1], height)];
return data.map(n => convert(n));
}
if (typeof data === "object") {
const result = {};
for (let key in data) {
if (key === "rotation") result[key] = data[key];
else if (key.startsWith("height") || key === "y" || key.endsWith("Y")) result[key] = convert(data[key], height);
else result[key] = convert(data[key]);
}
return result;
}
return data;
};

const delay = n => new Promise(resolve => setTimeout(resolve, n));

// good idea, but it doesn't work :(
const emulateClick = source => {
const event = document.createEvent("CustomEvent");
Expand All @@ -40,6 +107,93 @@ const clickRect = () => {
rect.fire("click", { clientX: 10, clientY: 10 });
};

/**
* Click once on the main Stage
* @param {number} x
* @param {number} y
* @param {function} done
*/
const clickKonva = (x, y, done) => {
const stage = window.Konva.stages[0];
stage.fire("click", { clientX: x, clientY: y, evt: { offsetX: x, offsetY: y } });
done();
};

/**
* Click multiple times on the Stage
* @param {number[][]} points array of coords arrays ([[x1, y1], [x2, y2], ...])
* @param {function} done
*/
const clickMultipleKonva = async (points, done) => {
const stage = window.Konva.stages[0];
for (let point of points) {
stage.fire("click", { evt: { offsetX: point[0], offsetY: point[1] } });
// await delay(10);
}
done();
};

/**
* Create Polygon on Stage by clicking multiple times and click on the first point at the end
* @param {number[][]} points array of coords arrays ([[x1, y1], [x2, y2], ...])
* @param {function} done
*/
const polygonKonva = async (points, done) => {
const delay = () => new Promise(resolve => setTimeout(resolve, 10));
const stage = window.Konva.stages[0];
const firstCoords = points[0];
for (let point of points) {
stage.fire("click", { evt: { offsetX: point[0], offsetY: point[1] } });
await delay();
}

// for closing the Polygon we should place cursor over the first point
const firstPoint = stage.getIntersection({ x: firstCoords[0], y: firstCoords[1] });
firstPoint.fire("mouseover");
await delay();
// and only after that we can click on it
firstPoint.fire("click");
done();
};

/**
* Click and hold, move the cursor (with one pause in the middle) and release the mouse
* @param {number} x
* @param {number} y
* @param {number} shiftX
* @param {number} shiftY
* @param {function} done
*/
const dragKonva = async (x, y, shiftX, shiftY, done) => {
const stage = window.Konva.stages[0];
stage.fire("mousedown", { evt: { offsetX: x, offsetY: y } });
// await delay(10);
stage.fire("mousemove", { evt: { offsetX: x + (shiftX >> 1), offsetY: y + (shiftY >> 1) } });
// await delay(10);
// we should move the cursor to the last point and only after that release the mouse
stage.fire("mousemove", { evt: { offsetX: x + shiftX, offsetY: y + shiftY } });
// await delay(10);
// because some events work on mousemove and not on mouseup
stage.fire("mouseup", { evt: { offsetX: x + shiftX, offsetY: y + shiftY } });
done();
};

const serialize = () => window.Htx.completionStore.selected.serializeCompletion();

module.exports = { initLabelStudio, emulateClick, clickRect, serialize };
module.exports = {
initLabelStudio,
waitForImage,
delay,

getSizeConvertor,
convertToFixed,

emulateClick,
clickRect,
clickKonva,
clickMultipleKonva,
polygonKonva,
dragKonva,

serialize,
};
155 changes: 155 additions & 0 deletions e2e/tests/image.shapes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/* global Feature, Scenario */

const {
initLabelStudio,
waitForImage,
getSizeConvertor,
convertToFixed,
clickKonva,
polygonKonva,
dragKonva,
serialize,
} = require("./helpers");

const assert = require("assert");

Feature("Test Image object");

const getConfigWithShape = (shape, props = "") => `
<View>
<Image name="img" value="$image" />
<${shape} ${props} name="tag" toName="img" />
</View>`;

const IMAGE =
"https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg";

// precalculated image size on the screen; may change because of different reasons
const WIDTH = 706;
const HEIGHT = 882;
const convertToImageSize = getSizeConvertor(WIDTH, HEIGHT);

const completionEmpty = {
id: "1000",
result: [],
};

const shapes = [
{
shape: "KeyPoint",
props: 'strokeWidth="5"',
action: clickKonva,
regions: [
{
params: [200, 100],
result: { x: 200, y: 100, width: 5 },
},
{
params: [100, 100],
result: { x: 100, y: 100, width: 5 },
},
],
},
{
shape: "Polygon",
action: polygonKonva,
regions: [
{
// outer array — params, inner array — points as the first param
params: [
[
[200, 20],
[400, 100],
[300, 200],
],
],
result: {
points: [
[200, 20],
[400, 100],
[300, 200],
],
},
},
{
// outer array — params, inner array — points as the first param
params: [
[
[400, 10],
[400, 90],
[370, 30],
[300, 10],
],
],
result: {
points: [
[400, 10],
[400, 90],
[370, 30],
[300, 10],
],
},
},
],
},
{
shape: "Rectangle",
action: dragKonva,
regions: [
{
params: [100, 210, 80, 30],
result: { width: 80, height: 30, rotation: 0, x: 100, y: 210 },
},
{
params: [100, 350, -50, -50],
result: { width: 50, height: 50, rotation: 0, x: 50, y: 300 },
},
],
},
{
shape: "Ellipse",
action: dragKonva,
regions: [
{
params: [300, 300, 50, 50],
result: { radiusX: 50, radiusY: 50, rotation: 0, x: 300, y: 300 },
},
{
// @todo Ellipse behave differently depending on direction of drawing
// it keeps center at the start point on right-down movement
// and it moves center after the cursor on left-up movement
// @todo looks like a bug
params: [230, 300, -50, -30],
result: { radiusX: 50, radiusY: 30, rotation: 0, x: 180, y: 270 },
},
],
},
];

Scenario("Simple shapes on Image", async function(I) {
for (let shape of shapes) {
const params = {
config: getConfigWithShape(shape.shape, shape.props),
data: { image: IMAGE },
completions: [completionEmpty],
};

I.amOnPage("/");
await I.executeAsyncScript(initLabelStudio, params);
// canvas won't be initialized fully before the image loads
await I.executeAsyncScript(waitForImage);
I.waitForVisible("canvas");
I.see("Regions (0)");

for (let region of shape.regions) {
// draw the shape using corresponding helper and params
await I.executeAsyncScript(shape.action, ...region.params);
}

const result = await I.executeScript(serialize);
for (let i = 0; i < shape.regions.length; i++) {
assert.equal(result[i].type, shape.shape.toLowerCase());
assert.deepEqual(convertToFixed(result[i].value), convertToImageSize(shape.regions[i].result));
}
}
});