diff --git a/tests/cypress/e2e/features/masks_basics.js b/tests/cypress/e2e/features/masks_basics.js index d49b678770e6..b593cd43e131 100644 --- a/tests/cypress/e2e/features/masks_basics.js +++ b/tests/cypress/e2e/features/masks_basics.js @@ -1,4 +1,4 @@ -// Copyright (C) 2022 CVAT.ai Corporation +// Copyright (C) 2022-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -7,6 +7,7 @@ context('Manipulations with masks', { scrollBehavior: false }, () => { const taskName = 'Basic actions with masks'; const serverFiles = ['images/image_1.jpg', 'images/image_2.jpg', 'images/image_3.jpg']; + const drawingActions = [{ method: 'brush', coordinates: [[300, 300], [700, 300], [700, 700], [300, 700]], @@ -73,12 +74,12 @@ context('Manipulations with masks', { scrollBehavior: false }, () => { }); }); - beforeEach(() => { - cy.removeAnnotations(); - cy.goCheckFrameNumber(0); - }); + describe('Tests to make sure that basic features work with masks', () => { + beforeEach(() => { + cy.removeAnnotations(); + cy.goCheckFrameNumber(0); + }); - describe('Draw a couple of masks masks', () => { it('Drawing a couple of masks. Save job, reopen job, masks must exist', () => { cy.startMaskDrawing(); cy.drawMask(drawingActions); @@ -167,4 +168,155 @@ context('Manipulations with masks', { scrollBehavior: false }, () => { cy.finishMaskDrawing(); }); }); + + describe('Tests to make sure that empty masks cannot be created', () => { + beforeEach(() => { + cy.removeAnnotations(); + }); + + function checkEraseTools(baseTool = '.cvat-brush-tools-brush', disabled = true) { + cy.get(baseTool).should('have.class', 'cvat-brush-tools-active-tool'); + + const condition = disabled ? 'be.disabled' : 'not.be.disabled'; + cy.get('.cvat-brush-tools-eraser').should(condition); + cy.get('.cvat-brush-tools-polygon-minus').should(condition); + } + + function checkMaskNotEmpty(selector) { + cy.get(selector).should('exist').and('be.visible'); + cy.get(selector) + .should('have.attr', 'height') + .then((height) => { + expect(+height).to.be.gt(1); + }); + cy.get(selector) + .should('have.attr', 'width') + .then((width) => { + expect(+width).to.be.gt(1); + }); + } + + it('Erase tools are locked when nothing to erase', () => { + const erasedMask = [{ + method: 'brush', + coordinates: [[450, 250], [600, 400], [450, 550], [300, 400]], + }, { + method: 'polygon-minus', + coordinates: [[100, 100], [700, 100], [700, 700], [100, 700]], + }]; + + cy.startMaskDrawing(); + checkEraseTools(); + cy.drawMask(erasedMask); + + cy.get('.cvat-brush-tools-brush').click(); + checkEraseTools(); + + cy.finishMaskDrawing(); + cy.get('#cvat_canvas_shape_1').should('not.exist'); + }); + + it('Drawing a mask, finish with erasing tool. On new mask drawing tool is reset', () => { + const masks = [[{ + method: 'brush', + coordinates: [[450, 250], [600, 400], [450, 550], [300, 400]], + }, { + method: 'polygon-minus', + coordinates: [[100, 100], [400, 100], [400, 400], [100, 400]], + }], [{ + method: 'brush', + coordinates: [[550, 350], [700, 500], [550, 650], [400, 500]], + }, { + method: 'eraser', + coordinates: [[550, 350]], + }]]; + + for (const [index, mask] of masks.entries()) { + cy.startMaskDrawing(); + cy.drawMask(mask); + cy.finishMaskDrawing(); + + cy.get(`#cvat_canvas_shape_${index + 1}`).should('exist').and('be.visible'); + + cy.startMaskDrawing(); + checkEraseTools(); + cy.finishMaskDrawing(); + } + }); + + it('Empty masks are deleted using remove underlying pixels feature', () => { + const masks = [[{ + method: 'brush', + coordinates: [[150, 150], [270, 270]], + }], [{ + method: 'brush', + coordinates: [[350, 350], [370, 370]], + }], [{ + method: 'polygon-plus', + coordinates: [[100, 100], [400, 100], [400, 400], [100, 400]], + }]]; + + cy.startMaskDrawing(); + cy.get('.cvat-brush-tools-underlying-pixels').click(); + cy.get('.cvat-brush-tools-underlying-pixels').should('have.class', 'cvat-brush-tools-active-tool'); + cy.finishMaskDrawing(); + + for (const [index, mask] of masks.entries()) { + cy.startMaskDrawing(); + cy.drawMask(mask); + cy.finishMaskDrawing(); + + cy.get(`#cvat_canvas_shape_${index + 1}`).should('exist').and('be.visible'); + } + + // Fist mask is updated, second mask is removed after third mask is drawn + cy.contains('Some objects were deleted').should('exist').and('be.visible'); + for (const id of [1, 3]) { + cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible'); + } + cy.get('#cvat_canvas_shape_2').should('not.exist'); + cy.saveJob('PATCH', 200, 'removeUnderlyingPixelsUndoRedo'); + + // Undo creating mask, second mask is restored + cy.contains('.cvat-annotation-header-button', 'Undo').click(); + for (const id of [1, 2]) { + cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible'); + } + cy.saveJob('PATCH', 200, 'removeUnderlyingPixelsUndoRedo'); + + // Redo creating mask, second mask is removed + cy.contains('.cvat-annotation-header-button', 'Redo').click(); + for (const id of [1, 3]) { + cy.get(`#cvat_canvas_shape_${id}`).should('exist').and('be.visible'); + } + cy.get('#cvat_canvas_shape_2').should('not.exist'); + cy.saveJob('PATCH', 200, 'removeUnderlyingPixelsUndoRedo'); + + cy.get('.cvat-notification-notice-save-annotations-failed').should('not.exist'); + }); + + it('Erasing a mask during editing is not allowed', () => { + const mask = [{ + method: 'brush', + coordinates: [[450, 250], [600, 400], [450, 550], [300, 400]], + }]; + const eraseAction = [{ + method: 'polygon-minus', + coordinates: [[100, 100], [700, 100], [700, 700], [100, 700]], + }]; + + cy.startMaskDrawing(); + cy.drawMask(mask); + cy.finishMaskDrawing(); + + cy.get('#cvat-objects-sidebar-state-item-1').within(() => { + cy.get('[aria-label="more"]').trigger('mouseover'); + }); + cy.get('.cvat-object-item-menu').last().should('be.visible').contains('button', 'Edit').click(); + cy.drawMask(eraseAction); + cy.finishMaskDrawing(); + + checkMaskNotEmpty('#cvat_canvas_shape_1'); + }); + }); });