From 95c20d194f5154d89e03cc8f1684536dc054a5a4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 2 Sep 2022 12:56:51 +0300 Subject: [PATCH] Added some JEST tests to cover skeleton basic features (#4865) * Added tests to create/get a task with skeletons * Added some dummy data for tests * Added a couple of tests * Aborted extra changes * Added a couple of put tests * Removed extra lines * Added test to check object state logic for complex objects * Updated eslint config file * Fixed config * Removed extra space * Fixed lint-staged config --- cvat-core/.eslintrc.js | 12 +- cvat-core/tests/api/annotations.js | 99 +++++++ cvat-core/tests/api/object-state.js | 37 +++ cvat-core/tests/api/tasks.js | 80 ++++- cvat-core/tests/mocks/dummy-data.mock.js | 353 +++++++++++++++++++++++ lint-staged.config.js | 4 +- 6 files changed, 570 insertions(+), 15 deletions(-) diff --git a/cvat-core/.eslintrc.js b/cvat-core/.eslintrc.js index 8e110cfffaf..a28a5cc1b80 100644 --- a/cvat-core/.eslintrc.js +++ b/cvat-core/.eslintrc.js @@ -3,9 +3,6 @@ // SPDX-License-Identifier: MIT module.exports = { - env: { - 'jest/globals': true, - }, ignorePatterns: [ '.eslintrc.js', 'webpack.config.js', @@ -14,17 +11,10 @@ module.exports = { 'src/3rdparty/**', 'node_modules/**', 'dist/**', + 'tests/**/*.js', ], parserOptions: { project: './tsconfig.json', tsconfigRootDir: __dirname, }, - ignorePatterns: ['tests/**/*.js'], - plugins: ['jest'], - rules: { - 'jest/no-disabled-tests': 'warn', - 'jest/no-focused-tests': 'error', - 'jest/no-identical-title': 'error', - 'jest/prefer-to-have-length': 'warn', - } }; diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index f07d9e184f0..de2b1257c3f 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -65,6 +65,28 @@ describe('Feature: get annotations', () => { expect(annotations).toHaveLength(1); expect(annotations[0].shapeType).toBe('ellipse'); }); + + test('get skeletons with a filter', async () => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + const annotations = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]')); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(2); + for (const object of annotations) { + expect(object.shapeType).toBe('skeleton'); + expect(object.elements).toBeInstanceOf(Array); + const label = object.label; + let points = []; + object.elements.forEach((element, idx) => { + expect(element).toBeInstanceOf(cvat.classes.ObjectState); + expect(element.label.id).toBe(label.structure.sublabels[idx].id); + expect(element.shapeType).toBe('points'); + points = [...points, ...element.points]; + }); + expect(points).toEqual(object.points); + } + + expect(annotations[0].shapeType).toBe('skeleton'); + }) }); describe('Feature: get interpolated annotations', () => { @@ -351,6 +373,68 @@ describe('Feature: put annotations', () => { zOrder: 0, })).toThrow(window.cvat.exceptions.ArgumentError); }); + + test('put a skeleton shape to a job', async() => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + const label = job.labels[0]; + await job.annotations.clear(true); + await job.annotations.clear(); + const skeleton = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ShapeType.SKELETON, + points: [], + label, + elements: label.structure.sublabels.map((sublabel, idx) => ({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ShapeType.POINTS, + points: [idx * 10, idx * 10], + label: sublabel, + })) + }); + + await job.annotations.put([skeleton]); + const annotations = await job.annotations.get(0); + expect(annotations.length).toBe(1); + expect(annotations[0].objectType).toBe(window.cvat.enums.ObjectType.SHAPE); + expect(annotations[0].shapeType).toBe(window.cvat.enums.ShapeType.SKELETON); + for (const element of annotations[0].elements) { + expect(element.objectType).toBe(window.cvat.enums.ObjectType.SHAPE); + expect(element.shapeType).toBe(window.cvat.enums.ShapeType.POINTS); + } + }); + + test('put a skeleton track to a task', async() => { + const task = (await window.cvat.tasks.get({ id: 40 }))[0]; + const label = task.labels[0]; + await task.annotations.clear(true); + await task.annotations.clear(); + const skeleton = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ShapeType.SKELETON, + points: [], + label, + elements: label.structure.sublabels.map((sublabel, idx) => ({ + frame: 0, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ShapeType.POINTS, + points: [idx * 10, idx * 10], + label: sublabel, + })) + }); + + await task.annotations.put([skeleton]); + const annotations = await task.annotations.get(2); + expect(annotations.length).toBe(1); + expect(annotations[0].objectType).toBe(window.cvat.enums.ObjectType.TRACK); + expect(annotations[0].shapeType).toBe(window.cvat.enums.ShapeType.SKELETON); + for (const element of annotations[0].elements) { + expect(element.objectType).toBe(window.cvat.enums.ObjectType.TRACK); + expect(element.shapeType).toBe(window.cvat.enums.ShapeType.POINTS); + } + }); }); describe('Feature: check unsaved changes', () => { @@ -772,6 +856,21 @@ describe('Feature: get statistics', () => { expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); expect(statistics.total.total).toBe(1012); }); + + test('get statistics from a job with skeletons', async () => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + await job.annotations.clear(true); + const statistics = await job.annotations.statistics(); + expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); + expect(statistics.total.total).toBe(30); + const labelName = job.labels[0].name; + expect(statistics.label[labelName].skeleton.shape).toBe(1); + expect(statistics.label[labelName].skeleton.track).toBe(1); + expect(statistics.label[labelName].manually).toBe(2); + expect(statistics.label[labelName].interpolated).toBe(3); + expect(statistics.label[labelName].total).toBe(5); + + }); }); describe('Feature: select object', () => { diff --git a/cvat-core/tests/api/object-state.js b/cvat-core/tests/api/object-state.js index 977aaacebde..bb86279e855 100644 --- a/cvat-core/tests/api/object-state.js +++ b/cvat-core/tests/api/object-state.js @@ -282,3 +282,40 @@ describe('Feature: delete object', () => { expect(annotationsAfter).toHaveLength(length - 1); }); }); + +describe('Feature: skeletons', () => { + test('lock, hide, occluded, outside for skeletons', async () => { + const job = (await window.cvat.jobs.get({ jobID: 40 }))[0]; + let [skeleton] = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]')); + expect(skeleton.shapeType).toBe('skeleton'); + skeleton.lock = true; + skeleton.outside = true; + skeleton.occluded = true; + skeleton.hidden = true; + skeleton = await skeleton.save(); + expect(skeleton.lock).toBe(true); + expect(skeleton.outside).toBe(true); + expect(skeleton.occluded).toBe(true); + expect(skeleton.hidden).toBe(true); + expect(skeleton.elements).toBeInstanceOf(Array); + expect(skeleton.elements.length).toBe(skeleton.label.structure.sublabels.length); + for (const element of skeleton.elements) { + expect(element.lock).toBe(true); + expect(element.outside).toBe(true); + expect(element.occluded).toBe(true); + expect(element.hidden).toBe(true); + } + + skeleton.elements[0].lock = false; + skeleton.elements[0].outside = false; + skeleton.elements[0].occluded = false; + skeleton.elements[0].hidden = false; + skeleton.elements[0].save(); + + [skeleton] = await job.annotations.get(0, false, JSON.parse('[{"and":[{"==":[{"var":"shape"},"skeleton"]}]}]')); + expect(skeleton.lock).toBe(false); + expect(skeleton.outside).toBe(false); + expect(skeleton.occluded).toBe(false); + expect(skeleton.hidden).toBe(false); + }); +}); \ No newline at end of file diff --git a/cvat-core/tests/api/tasks.js b/cvat-core/tests/api/tasks.js index ea1299dda4c..bbb990c81cf 100644 --- a/cvat-core/tests/api/tasks.js +++ b/cvat-core/tests/api/tasks.js @@ -18,7 +18,7 @@ describe('Feature: get a list of tasks', () => { test('get all tasks', async () => { const result = await window.cvat.tasks.get(); expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(6); + expect(result).toHaveLength(7); for (const el of result) { expect(el).toBeInstanceOf(Task); } @@ -34,6 +34,33 @@ describe('Feature: get a list of tasks', () => { expect(result[0].id).toBe(3); }); + test('get a task with skeletons by an id', async () => { + const result = await window.cvat.tasks.get({ + id: 40, + }); + + expect(Array.isArray(result)).toBeTruthy(); + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(Task); + expect(result[0].id).toBe(40); + expect(result[0].labels).toBeInstanceOf(Array); + + for (const label of result[0].labels) { + expect(label).toBeInstanceOf(window.cvat.classes.Label); + if (label.type === 'skeleton') { + expect(label.hasParent).toBe(false); + expect(label.structure.sublabels).toBeInstanceOf(Array); + expect(typeof label.structure.svg).toBe('string'); + expect(label.structure.svg.length).not.toBe(0); + + for (const sublabel of label.structure.sublabels) { + expect(sublabel).toBeInstanceOf(window.cvat.classes.Label); + expect(sublabel.hasParent).toBe(true); + } + } + } + }); + test('get a task by an unknown id', async () => { const result = await window.cvat.tasks.get({ id: 50, @@ -154,12 +181,61 @@ describe('Feature: save a task', () => { project_id: 2, bug_tracker: 'bug tracker value', image_quality: 50, - z_order: true, }); const result = await task.save(); expect(result.projectId).toBe(2); }); + + test('create a new task with skeletons', async () => { + const svgSpec = ` + + + + + + + + + + + `; + + const task = new window.cvat.classes.Task({ + name: 'task with skeletons', + labels: [{ + name: 'star skeleton', + type: 'skeleton', + attributes: [], + svg: svgSpec, + sublabels: [{ + name: '1', + type: 'points', + attributes: [] + }, { + name: '2', + type: 'points', + attributes: [] + }, { + name: '3', + type: 'points', + attributes: [] + }, { + name: '4', + type: 'points', + attributes: [] + }, { + name: '5', + type: 'points', + attributes: [] + }] + }], + project_id: null, + }); + + const result = await task.save(); + expect(typeof result.id).toBe('number'); + }); }); describe('Feature: delete a task', () => { diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js index 4655f57e723..2b4edfff1d2 100644 --- a/cvat-core/tests/mocks/dummy-data.mock.js +++ b/cvat-core/tests/mocks/dummy-data.mock.js @@ -783,6 +783,105 @@ const tasksDummyData = { stop_frame: 5001, frame_filter: '', }, + { + url: 'http://localhost:7000/api/tasks/40', + id: 40, + name: 'test', + project_id: null, + mode: 'annotation', + owner: { + url: 'http://localhost:7000/api/users/1', + id: 1, + username: 'admin', + first_name: '', + last_name: '', + }, + assignee: null, + bug_tracker: '', + created_date: '2022-08-25T12:10:45.471663Z', + updated_date: '2022-08-25T12:10:45.993989Z', + overlap: 0, + segment_size: 4, + status: 'annotation', + labels: [{ + id: 54, + name: 'star skeleton', + color: '#9cb75a', + attributes: [], + type: 'skeleton', + sublabels: [{ + id: 55, + name: '1', + color: '#d12345', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 56, + name: '2', + color: '#350dea', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 57, + name: '3', + color: '#479ffe', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 58, + name: '4', + color: '#4a649f', + attributes: [], + type: 'points', + has_parent: true + }, { + id: 59, + name: '5', + color: '#478144', + attributes: [], + type: 'points', + has_parent: true + }], + has_parent: false, + svg: + ` + + + + + + + + + ` + }], + segments: [{ + start_frame: 0, + stop_frame: 3, + jobs: [{ + url: 'http://localhost:7000/api/jobs/40', + id: 40, + assignee: null, + status: 'annotation', + stage: 'annotation', + state: 'new', + }] + }], + data_chunk_size: 17, + data_compressed_chunk_type: 'imageset', + data_original_chunk_type: 'imageset', + size: 4, + image_quality: 70, + data: 12, + dimension: '2d', + subset: '', + organization: null, + target_storage: null, + source_storage: null + }, { url: 'http://localhost:7000/api/tasks/3', id: 3, @@ -2532,6 +2631,230 @@ const taskAnnotationsDummyData = { ], tracks: [], }, + 40: { + version: 0, + tags: [], + shapes: [{ + type: 'skeleton', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [], + id: 23, + frame: 0, + label_id: 54, + group: 0, + source: 'manual', + attributes: [], + elements: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 908.0654296875, + 768.8268729552019 + ], + id: 24, + frame: 0, + label_id: 55, + group: 0, + source: 'manual', + attributes: [] + }, { + type: "points", + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 1230.1533057030483, + 523.7802734375 + ], + id: 25, + frame: 0, + label_id: 56, + group: 0, + source: 'manual', + attributes: [] + }, { + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 1525.9969940892624, + 772.6557444966547 + ], + id: 26, + frame: 0, + label_id: 57, + group: 0, + source: 'manual', + attributes: [] + }, { + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 1468.7369236136856, + 1270.4066429432623 + ], + id: 27, + frame: 0, + label_id: 58, + group: 0, + source: 'manual', + attributes: [] + }, { + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 989.1838401839595, + 1258.9201156622657 + ], + id: 28, + frame: 0, + label_id:59, + group: 0, + source: 'manual', + attributes: [] + }] + }], + tracks: [{ + id: 1, + frame: 0, + label_id: 54, + group: 0, + source: 'manual', + shapes: [{ + type: 'skeleton', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [], + id: 1, + frame: 0, + attributes: [] + }], + attributes: [], + elements: [{ + id: 2, + frame: 0, + label_id: 55, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 88.4140625, + 332.85145482411826 + ], + id: 2, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 3, + frame: 0, + label_id: 56, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 437.3386217629577, + 96.447265625 + ], + id: 3, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 4, + frame: 0, + label_id: 57, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 757.8323014937105, + 336.54528805456357 + ], + id: 4, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 5, + frame: 0, + label_id: 58, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 695.8012648051717, + 816.7412907822327 + ], + id: 5, + frame: 0, + attributes: [] + }], + attributes: [] + }, { + id: 6, + frame: 0, + label_id: 59, + group: 0, + source: 'manual', + shapes: [{ + type: 'points', + occluded: false, + outside: false, + z_order: 0, + rotation: 0.0, + points: [ + 176.29133990867558, + 805.659875353811 + ], + id: 6, + frame: 0, + attributes: [] + }], + attributes: [] + }] + }] + } }; const jobAnnotationsDummyData = JSON.parse(JSON.stringify(taskAnnotationsDummyData)); @@ -2629,6 +2952,36 @@ const frameMetaDummyData = { }, ], }, + 40: { + chunk_size: 17, + size: 4, + image_quality: 70, + start_frame: 0, + stop_frame: 3, + frame_filter: '', + frames: [{ + width: 2560, + height: 1703, + name: '1598296101_1033667.jpg', + has_related_context: false + }, { + width: 1600, + height: 1200, + name: '30fdce7f27b9c7b1d50108d7c16d23ef.jpg', + has_related_context: false + }, { + width: 2880, + height: 1800, + name: '567362-ily-comedy-drama-1finding-3.jpg', + has_related_context: false + }, { + width: 1920, + height: 1080, + name: '730443-under-the-sea-wallpapers-1920x1080-windows-10.jpg', + has_related_context: false + }], + deleted_frames: [] + }, 100: { chunk_size: 36, size: 9, diff --git a/lint-staged.config.js b/lint-staged.config.js index de7bc0c4c53..e2e443718d1 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -16,9 +16,9 @@ module.exports = (stagedFiles) => { const eslintFiles = micromatch(stagedFiles, eslintExtensions); const scssFiles = micromatch(stagedFiles, scssExtensions); - const tests = containsInPath('/tests/', eslintFiles); + const tests = containsInPath('/tests/cypress', eslintFiles); const cvatData = containsInPath('/cvat-data/', eslintFiles); - const cvatCore = containsInPath('/cvat-core/', eslintFiles); + const cvatCore = containsInPath('/cvat-core/src', eslintFiles); const cvatCanvas = containsInPath('/cvat-canvas/', eslintFiles); const cvatCanvas3d = containsInPath('/cvat-canvas3d/', eslintFiles); const cvatUI = containsInPath('/cvat-ui/', eslintFiles);