From d87729c7e62ff9bda4ba9e7434dfcdb3f9f78279 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 2 Jun 2021 15:28:38 +0300 Subject: [PATCH 01/10] migration added --- .../README.md | 45 ++++++ .../schema.prisma | 135 ++++++++++++++++++ .../steps.json | 52 +++++++ prisma/migrations/migrate.lock | 3 +- prisma/schema.prisma | 4 +- 5 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20210529192323-image-compare-config-as-json/README.md create mode 100644 prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma create mode 100644 prisma/migrations/20210529192323-image-compare-config-as-json/steps.json diff --git a/prisma/migrations/20210529192323-image-compare-config-as-json/README.md b/prisma/migrations/20210529192323-image-compare-config-as-json/README.md new file mode 100644 index 00000000..b815e81b --- /dev/null +++ b/prisma/migrations/20210529192323-image-compare-config-as-json/README.md @@ -0,0 +1,45 @@ +# Migration `20210529192323-image-compare-config-as-json` + +This migration has been generated by Pavel Strunkin at 5/29/2021, 10:23:24 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE "Project" DROP COLUMN "diffDimensionsFeature", +DROP COLUMN "ignoreAntialiasing", +DROP COLUMN "threshold", +ADD COLUMN "imageComparisonConfig" TEXT NOT NULL DEFAULT E'{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }' +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20210517203552-add-custom-tags..20210529192323-image-compare-config-as-json +--- datamodel.dml ++++ datamodel.dml +@@ -3,9 +3,9 @@ + } + datasource db { + provider = "postgresql" +- url = "***" ++ url = "***" + } + model Build { + id String @id @default(uuid()) +@@ -35,12 +35,10 @@ + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + // config + autoApproveFeature Boolean @default(false) +- diffDimensionsFeature Boolean @default(false) +- ignoreAntialiasing Boolean @default(true) +- threshold Float @default(0.1) + imageComparison ImageComparison @default(pixelmatch) ++ imageComparisonConfig String @default("{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }") + @@unique([name]) + } +``` + + diff --git a/prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma b/prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma new file mode 100644 index 00000000..59f542b7 --- /dev/null +++ b/prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma @@ -0,0 +1,135 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = "***" +} + +model Build { + id String @id @default(uuid()) + ciBuildId String? + number Int? + branchName String? + status String? + testRuns TestRun[] + projectId String + project Project @relation(fields: [projectId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + user User? @relation(fields: [userId], references: [id]) + userId String? + isRunning Boolean? + + @@unique([projectId, ciBuildId]) +} + +model Project { + id String @id @default(uuid()) + name String + mainBranchName String @default("master") + builds Build[] + buildsCounter Int @default(0) + testVariations TestVariation[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + // config + autoApproveFeature Boolean @default(false) + imageComparison ImageComparison @default(pixelmatch) + imageComparisonConfig String @default("{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }") + + @@unique([name]) +} + +model TestRun { + id String @id @default(uuid()) + imageName String + diffName String? + diffPercent Float? + diffTollerancePercent Float @default(0) + pixelMisMatchCount Int? + status TestStatus + buildId String + build Build @relation(fields: [buildId], references: [id]) + testVariationId String? + testVariation TestVariation? @relation(fields: [testVariationId], references: [id]) + merge Boolean @default(false) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + // Test variation data + name String @default("") + browser String? + device String? + os String? + viewport String? + customTags String? @default("") + baselineName String? + comment String? + baseline Baseline? + branchName String @default("master") + baselineBranchName String? + ignoreAreas String @default("[]") + tempIgnoreAreas String @default("[]") +} + +model TestVariation { + id String @id @default(uuid()) + name String + branchName String @default("master") + browser String @default("") + device String @default("") + os String @default("") + viewport String @default("") + customTags String @default("") + baselineName String? + ignoreAreas String @default("[]") + projectId String + project Project @relation(fields: [projectId], references: [id]) + testRuns TestRun[] + baselines Baseline[] + comment String? + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@unique([projectId, name, browser, device, os, viewport, customTags, branchName]) +} + +model Baseline { + id String @id @default(uuid()) + baselineName String + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + testRunId String? + testRun TestRun? @relation(fields: [testRunId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +model User { + id String @id @default(uuid()) + email String @unique + password String + firstName String? + lastName String? + apiKey String @unique + isActive Boolean @default(true) + builds Build[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +enum TestStatus { + failed + new + ok + unresolved + approved + autoApproved +} + +enum ImageComparison { + pixelmatch + lookSame + odiff +} diff --git a/prisma/migrations/20210529192323-image-compare-config-as-json/steps.json b/prisma/migrations/20210529192323-image-compare-config-as-json/steps.json new file mode 100644 index 00000000..897f2153 --- /dev/null +++ b/prisma/migrations/20210529192323-image-compare-config-as-json/steps.json @@ -0,0 +1,52 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "Project", + "field": "imageComparisonConfig", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Project", + "field": "imageComparisonConfig" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Project", + "field": "imageComparisonConfig" + }, + "directive": "default" + }, + "argument": "", + "value": "\"{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }\"" + }, + { + "tag": "DeleteField", + "model": "Project", + "field": "diffDimensionsFeature" + }, + { + "tag": "DeleteField", + "model": "Project", + "field": "ignoreAntialiasing" + }, + { + "tag": "DeleteField", + "model": "Project", + "field": "threshold" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 3ba4c1d4..38b3adc5 100644 --- a/prisma/migrations/migrate.lock +++ b/prisma/migrations/migrate.lock @@ -17,4 +17,5 @@ 20210228121726-test-run--nullable-test-variation-id 20210405171118-github_243-set-empty-test-variation-tags-instead-of-null 20210425191116-github_215_project_config -20210517203552-add-custom-tags \ No newline at end of file +20210517203552-add-custom-tags +20210529192323-image-compare-config-as-json \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cdf2cd1b..4bb41db3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -36,10 +36,8 @@ model Project { createdAt DateTime @default(now()) // config autoApproveFeature Boolean @default(false) - diffDimensionsFeature Boolean @default(false) - ignoreAntialiasing Boolean @default(true) - threshold Float @default(0.1) imageComparison ImageComparison @default(pixelmatch) + imageComparisonConfig String @default("{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }") @@unique([name]) } From 1010bac5e07ae120f5d6b5ff8fc689c24e75e562 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 22:35:32 +0300 Subject: [PATCH 02/10] migration updated --- .../README.md | 10 +++++----- .../schema.prisma | 2 +- .../steps.json | 2 +- prisma/migrations/migrate.lock | 2 +- prisma/schema.prisma | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename prisma/migrations/{20210529192323-image-compare-config-as-json => 20210605124856-image-compare-config-as-json}/README.md (71%) rename prisma/migrations/{20210529192323-image-compare-config-as-json => 20210605124856-image-compare-config-as-json}/schema.prisma (96%) rename prisma/migrations/{20210529192323-image-compare-config-as-json => 20210605124856-image-compare-config-as-json}/steps.json (89%) diff --git a/prisma/migrations/20210529192323-image-compare-config-as-json/README.md b/prisma/migrations/20210605124856-image-compare-config-as-json/README.md similarity index 71% rename from prisma/migrations/20210529192323-image-compare-config-as-json/README.md rename to prisma/migrations/20210605124856-image-compare-config-as-json/README.md index b815e81b..70b49eff 100644 --- a/prisma/migrations/20210529192323-image-compare-config-as-json/README.md +++ b/prisma/migrations/20210605124856-image-compare-config-as-json/README.md @@ -1,6 +1,6 @@ -# Migration `20210529192323-image-compare-config-as-json` +# Migration `20210605124856-image-compare-config-as-json` -This migration has been generated by Pavel Strunkin at 5/29/2021, 10:23:24 PM. +This migration has been generated by Pavel Strunkin at 6/5/2021, 3:48:56 PM. You can check out the [state of the schema](./schema.prisma) after the migration. ## Database Steps @@ -9,14 +9,14 @@ You can check out the [state of the schema](./schema.prisma) after the migration ALTER TABLE "Project" DROP COLUMN "diffDimensionsFeature", DROP COLUMN "ignoreAntialiasing", DROP COLUMN "threshold", -ADD COLUMN "imageComparisonConfig" TEXT NOT NULL DEFAULT E'{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }' +ADD COLUMN "imageComparisonConfig" TEXT NOT NULL DEFAULT E'{ "threshold": 0.1, "ignoreAntialiasing": true, "allowDiffDimensions": false }' ``` ## Changes ```diff diff --git schema.prisma schema.prisma -migration 20210517203552-add-custom-tags..20210529192323-image-compare-config-as-json +migration 20210517203552-add-custom-tags..20210605124856-image-compare-config-as-json --- datamodel.dml +++ datamodel.dml @@ -3,9 +3,9 @@ @@ -37,7 +37,7 @@ migration 20210517203552-add-custom-tags..20210529192323-image-compare-config-as - ignoreAntialiasing Boolean @default(true) - threshold Float @default(0.1) imageComparison ImageComparison @default(pixelmatch) -+ imageComparisonConfig String @default("{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }") ++ imageComparisonConfig String @default("{ \"threshold\": 0.1, \"ignoreAntialiasing\": true, \"allowDiffDimensions\": false }") @@unique([name]) } ``` diff --git a/prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma b/prisma/migrations/20210605124856-image-compare-config-as-json/schema.prisma similarity index 96% rename from prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma rename to prisma/migrations/20210605124856-image-compare-config-as-json/schema.prisma index 59f542b7..f1422676 100644 --- a/prisma/migrations/20210529192323-image-compare-config-as-json/schema.prisma +++ b/prisma/migrations/20210605124856-image-compare-config-as-json/schema.prisma @@ -37,7 +37,7 @@ model Project { // config autoApproveFeature Boolean @default(false) imageComparison ImageComparison @default(pixelmatch) - imageComparisonConfig String @default("{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }") + imageComparisonConfig String @default("{ \"threshold\": 0.1, \"ignoreAntialiasing\": true, \"allowDiffDimensions\": false }") @@unique([name]) } diff --git a/prisma/migrations/20210529192323-image-compare-config-as-json/steps.json b/prisma/migrations/20210605124856-image-compare-config-as-json/steps.json similarity index 89% rename from prisma/migrations/20210529192323-image-compare-config-as-json/steps.json rename to prisma/migrations/20210605124856-image-compare-config-as-json/steps.json index 897f2153..d6b38056 100644 --- a/prisma/migrations/20210529192323-image-compare-config-as-json/steps.json +++ b/prisma/migrations/20210605124856-image-compare-config-as-json/steps.json @@ -31,7 +31,7 @@ "directive": "default" }, "argument": "", - "value": "\"{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }\"" + "value": "\"{ \\\"threshold\\\": 0.1, \\\"ignoreAntialiasing\\\": true, \\\"allowDiffDimensions\\\": false }\"" }, { "tag": "DeleteField", diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 38b3adc5..e6b5d6fa 100644 --- a/prisma/migrations/migrate.lock +++ b/prisma/migrations/migrate.lock @@ -18,4 +18,4 @@ 20210405171118-github_243-set-empty-test-variation-tags-instead-of-null 20210425191116-github_215_project_config 20210517203552-add-custom-tags -20210529192323-image-compare-config-as-json \ No newline at end of file +20210605124856-image-compare-config-as-json \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4bb41db3..cdb9a88b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -37,7 +37,7 @@ model Project { // config autoApproveFeature Boolean @default(false) imageComparison ImageComparison @default(pixelmatch) - imageComparisonConfig String @default("{ threshold: 0.1, ignoreAntialiasing: true, allowDiffDimensions: false }") + imageComparisonConfig String @default("{ \"threshold\": 0.1, \"ignoreAntialiasing\": true, \"allowDiffDimensions\": false }") @@unique([name]) } From 22ae2276acc168dee6b92bd7f563167ac812f55d Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 22:47:56 +0300 Subject: [PATCH 03/10] refactoring --- src/compare/compare.service.spec.ts | 5 +-- .../pixelmatch.service.spec.ts | 16 ++++----- .../{ => pixelmatch}/pixelmatch.service.ts | 34 ++++++++++++++----- .../libs/pixelmatch/pixelmatch.types.ts | 6 ++++ src/compare/utils/index.ts | 4 +-- src/shared/static/static.service.ts | 12 +++++-- 6 files changed, 54 insertions(+), 23 deletions(-) rename src/compare/libs/{ => pixelmatch}/pixelmatch.service.spec.ts (94%) rename src/compare/libs/{ => pixelmatch}/pixelmatch.service.ts (62%) create mode 100644 src/compare/libs/pixelmatch/pixelmatch.types.ts diff --git a/src/compare/compare.service.spec.ts b/src/compare/compare.service.spec.ts index c852f3dd..5697e682 100644 --- a/src/compare/compare.service.spec.ts +++ b/src/compare/compare.service.spec.ts @@ -2,14 +2,15 @@ import { Test, TestingModule } from '@nestjs/testing'; import { PrismaService } from '../prisma/prisma.service'; import { StaticService } from '../shared/static/static.service'; import { CompareService } from './compare.service'; -import { PixelmatchService } from './libs/pixelmatch.service'; +import { LookSameService } from './libs/looks-same/looks-same.service'; +import { PixelmatchService } from './libs/pixelmatch/pixelmatch.service'; describe('CompareService', () => { let service: CompareService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [CompareService, PixelmatchService, StaticService, PrismaService], + providers: [CompareService, PixelmatchService, LookSameService, StaticService, PrismaService], }).compile(); service = module.get(CompareService); diff --git a/src/compare/libs/pixelmatch.service.spec.ts b/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts similarity index 94% rename from src/compare/libs/pixelmatch.service.spec.ts rename to src/compare/libs/pixelmatch/pixelmatch.service.spec.ts index c1a9ca31..3b4673c8 100644 --- a/src/compare/libs/pixelmatch.service.spec.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts @@ -1,9 +1,9 @@ import { TestingModule, Test } from '@nestjs/testing'; -import { TestRun, TestStatus } from '@prisma/client'; +import { TestStatus } from '@prisma/client'; import Pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; import { mocked } from 'ts-jest/utils'; -import { StaticService } from '../../shared/static/static.service'; +import { StaticService } from '../../../shared/static/static.service'; import { PixelmatchService } from './pixelmatch.service'; jest.mock('pixelmatch'); @@ -37,7 +37,7 @@ describe('getDiff', () => { const getImageMock = jest.fn().mockReturnValueOnce(undefined).mockReturnValueOnce(image); service = await initService({ getImageMock }); - const result = service.getDiff( + const result = await service.getDiff( { baseline: null, image: 'image', @@ -65,7 +65,7 @@ describe('getDiff', () => { const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(image); service = await initService({ getImageMock }); - const result = service.getDiff( + const result = await service.getDiff( { baseline: 'image', image: 'image', @@ -97,7 +97,7 @@ describe('getDiff', () => { const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(baseline); service = await initService({ getImageMock }); - const result = service.getDiff( + const result = await service.getDiff( { baseline: 'image', image: 'image', @@ -136,7 +136,7 @@ describe('getDiff', () => { mocked(Pixelmatch).mockReturnValueOnce(5); service = await initService({ saveImageMock, getImageMock }); - const result = service.getDiff( + const result = await service.getDiff( { baseline: 'image', image: 'image', @@ -197,7 +197,7 @@ describe('getDiff', () => { const pixelMisMatchCount = 150; mocked(Pixelmatch).mockReturnValueOnce(pixelMisMatchCount); - const result = service.getDiff( + const result = await service.getDiff( { baseline: 'image', image: 'image', @@ -242,7 +242,7 @@ describe('getDiff', () => { getImageMock, }); - const result = service.getDiff( + const result = await service.getDiff( { baseline: 'image', image: 'image', diff --git a/src/compare/libs/pixelmatch.service.ts b/src/compare/libs/pixelmatch/pixelmatch.service.ts similarity index 62% rename from src/compare/libs/pixelmatch.service.ts rename to src/compare/libs/pixelmatch/pixelmatch.service.ts index ee6b3f10..e930051d 100644 --- a/src/compare/libs/pixelmatch.service.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.ts @@ -1,17 +1,33 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { TestStatus } from '@prisma/client'; import Pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; -import { StaticService } from '../../shared/static/static.service'; -import { DiffResult } from '../../test-runs/diffResult'; -import { scaleImageToSize, applyIgnoreAreas } from '../utils'; -import { ImageComparator, ImageCompareConfig, ImageCompareInput } from './image-comparator.interface'; +import { StaticService } from '../../../shared/static/static.service'; +import { DiffResult } from '../../../test-runs/diffResult'; +import { scaleImageToSize, applyIgnoreAreas } from '../../utils'; +import { ImageComparator } from '../image-comparator.interface'; +import { ImageCompareInput } from "../ImageCompareInput"; +import { PixelmatchConfig } from "./pixelmatch.types"; + +const DEFAULT_CONFIG: PixelmatchConfig = { threshold: 0.1, ignoreAntialiasing: true }; @Injectable() export class PixelmatchService implements ImageComparator { + private readonly logger: Logger = new Logger(PixelmatchService.name); + constructor(private staticService: StaticService) {} - getDiff(data: ImageCompareInput, config: ImageCompareConfig): DiffResult { + parseConfig(configJson: string): PixelmatchConfig { + let config: PixelmatchConfig = DEFAULT_CONFIG; + try { + config = JSON.parse(configJson); + } catch (ex) { + this.logger.error('Cannot parse config, fallback to default one ' + ex); + } + return config; + } + + async getDiff(data: ImageCompareInput, config: PixelmatchConfig): Promise { const result: DiffResult = { status: undefined, diffName: null, @@ -51,15 +67,15 @@ export class PixelmatchService implements ImageComparator { const scaledImage = scaleImageToSize(image, maxWidth, maxHeight); // apply ignore areas - const baselineData = applyIgnoreAreas(scaledBaseline, data.ignoreAreas); - const imageData = applyIgnoreAreas(scaledImage, data.ignoreAreas); + const baselineIgnored = applyIgnoreAreas(scaledBaseline, data.ignoreAreas); + const imageIgnored = applyIgnoreAreas(scaledImage, data.ignoreAreas); // compare const diff = new PNG({ width: maxWidth, height: maxHeight, }); - result.pixelMisMatchCount = Pixelmatch(baselineData, imageData, diff.data, maxWidth, maxHeight, { + result.pixelMisMatchCount = Pixelmatch(baselineIgnored.data, imageIgnored.data, diff.data, maxWidth, maxHeight, { includeAA: config.ignoreAntialiasing, threshold: config.threshold, }); diff --git a/src/compare/libs/pixelmatch/pixelmatch.types.ts b/src/compare/libs/pixelmatch/pixelmatch.types.ts new file mode 100644 index 00000000..c82e85a1 --- /dev/null +++ b/src/compare/libs/pixelmatch/pixelmatch.types.ts @@ -0,0 +1,6 @@ + +export interface PixelmatchConfig { + allowDiffDimensions?: boolean; + ignoreAntialiasing: boolean; + threshold: number; +} diff --git a/src/compare/utils/index.ts b/src/compare/utils/index.ts index fa88361f..6d06b90c 100644 --- a/src/compare/utils/index.ts +++ b/src/compare/utils/index.ts @@ -10,7 +10,7 @@ export function scaleImageToSize(image: PNG, width: number, height: number): PNG return image; } -export function applyIgnoreAreas(image: PNG, ignoreAreas: IgnoreAreaDto[]): Buffer { +export function applyIgnoreAreas(image: PNG, ignoreAreas: IgnoreAreaDto[]): PNG { ignoreAreas.forEach((area) => { for (let y = area.y; y < area.y + area.height; y++) { for (let x = area.x; x < area.x + area.width; x++) { @@ -22,5 +22,5 @@ export function applyIgnoreAreas(image: PNG, ignoreAreas: IgnoreAreaDto[]): Buff } } }); - return image.data; + return image; } diff --git a/src/shared/static/static.service.ts b/src/shared/static/static.service.ts index 609608c4..fa23a315 100644 --- a/src/shared/static/static.service.ts +++ b/src/shared/static/static.service.ts @@ -10,8 +10,8 @@ export class StaticService { private readonly logger: Logger = new Logger(StaticService.name); saveImage(type: 'screenshot' | 'diff' | 'baseline', imageBuffer: Buffer): string { - const imageName = `${Date.now()}.${type}.png`; - writeFileSync(this.getImagePath(imageName), imageBuffer); + const { imageName, imagePath } = this.generateNewImage(type); + writeFileSync(imagePath, imageBuffer); return imageName; } @@ -36,6 +36,14 @@ export class StaticService { }); } + private generateNewImage(type: 'screenshot' | 'diff' | 'baseline'): { imageName: string; imagePath: string } { + const imageName = `${Date.now()}.${type}.png`; + return { + imageName, + imagePath: this.getImagePath(imageName), + }; + } + private getImagePath(imageName: string): string { this.ensureDirectoryExistence(IMAGE_PATH); return path.resolve(IMAGE_PATH, imageName); From a486ac906c489dd3674b771b9d8a98da94a82997 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 22:48:15 +0300 Subject: [PATCH 04/10] looks-same added --- package-lock.json | 366 +++++++++++++++++++++++++--------------------- package.json | 5 +- 2 files changed, 201 insertions(+), 170 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9ba32a2..4fded886 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1048,9 +1048,9 @@ } }, "@nestjs/mapped-types": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-0.1.1.tgz", - "integrity": "sha512-FROYmmZ2F+tLJP/aHasPMX40iUHQPtEAzOAcfAp21baebN5iLUrdyTuphoXjIqubfPFSwtnAGpVm9kLJjQ//ig==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-0.4.0.tgz", + "integrity": "sha512-TVtd/aTb7EqPhVczdeuvzF9dY0fyE3ivvCstc2eO+AkNqrfzSG1kXYYiUUznKjd0qDa8g2TmPSmHUQ21AXsV1Q==" }, "@nestjs/passport": { "version": "7.1.5", @@ -1070,12 +1070,19 @@ } }, "@nestjs/platform-socket.io": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-7.6.3.tgz", - "integrity": "sha512-AuAVbyuRWJBDq/DExnQ9OOPEV/B5Akcfz9+uwe2cg2hHLN2+0iaLq/bSooy33XNp9feaNh4gxDTRQo5amqKW9g==", + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-7.6.17.tgz", + "integrity": "sha512-4uDVXp/D03h/ErSpviwMOfQ+CRPGcvOjgJmZ91aj1UxlKEbSOWpXjc0CKH5En0bjJTprIv9dsc2o/oKlm2lklQ==", "requires": { - "socket.io": "2.3.0", - "tslib": "2.0.3" + "socket.io": "2.4.1", + "tslib": "2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } } }, "@nestjs/schematics": { @@ -1117,12 +1124,12 @@ } }, "@nestjs/swagger": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-4.7.8.tgz", - "integrity": "sha512-mX955BiwkHUn8IKpqijCDGubx/vmUmADbK1D5pqo9o4hIdweKdqFK8ie6NkL5JNTxO2XOZfj6Dh6WUnDdCCEQg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-4.8.0.tgz", + "integrity": "sha512-YU+ahCOoOTZwSHrODHBiQDCqi7GWEjmSFg3Tot/lwVuQ321/3fIOz/lf+ehVQ5DFr7nVMhB7BRWFJLtE/+NhqQ==", "requires": { - "@nestjs/mapped-types": "0.1.1", - "lodash": "4.17.20", + "@nestjs/mapped-types": "0.4.0", + "lodash": "4.17.21", "path-to-regexp": "3.2.0" } }, @@ -2175,11 +2182,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2401,14 +2403,6 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2468,16 +2462,42 @@ "dev": true }, "browserslist": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz", - "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001165", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.621", + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", - "node-releases": "^1.1.67" + "node-releases": "^1.1.71" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30001228", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", + "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.738", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz", + "integrity": "sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw==", + "dev": true + }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + } } }, "bs-logger": { @@ -2564,11 +2584,6 @@ "get-intrinsic": "^1.0.0" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2581,12 +2596,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "caniuse-lite": { - "version": "1.0.30001170", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001170.tgz", - "integrity": "sha512-Dd4d/+0tsK0UNLrZs3CvNukqalnVTRrxb5mcQm8rHL49t7V5ZaTygwXkrq+FB+dVDf++4ri8eJnFEJAB8332PA==", - "dev": true - }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -2819,17 +2828,16 @@ "color-name": "~1.1.4" } }, + "color-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/color-diff/-/color-diff-1.2.0.tgz", + "integrity": "sha512-FN7iLBCfb97ElJU2AQXbBAFXPbKmu0XJjPU9GWWmUkIbXka+Im8Q5w1geiL9GB+AktJ4pIA6nRZD1+TlEG6/rA==" + }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", - "dev": true - }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -3271,12 +3279,6 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "electron-to-chromium": { - "version": "1.3.629", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.629.tgz", - "integrity": "sha512-iSPPJtPvHrMAvYOt+9cdbDmTasPqwnwz4lkP8Dn200gDNUBQOLQ96xUsWXBwXslAo5XxdoXAoQQ3RAy4uao9IQ==", - "dev": true - }, "emittery": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", @@ -3315,22 +3317,22 @@ } }, "engine.io": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", - "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", + "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "0.3.1", + "cookie": "~0.4.1", "debug": "~4.1.0", "engine.io-parser": "~2.2.0", - "ws": "^7.1.2" + "ws": "~7.4.2" }, "dependencies": { "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, "debug": { "version": "4.1.1", @@ -3339,13 +3341,18 @@ "requires": { "ms": "^2.1.1" } + }, + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" } } }, "engine.io-client": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz", - "integrity": "sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz", + "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==", "requires": { "component-emitter": "~1.3.0", "component-inherit": "0.0.3", @@ -3355,8 +3362,8 @@ "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~6.1.0", - "xmlhttprequest-ssl": "~1.5.4", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.6.2", "yeast": "0.1.2" }, "dependencies": { @@ -3378,23 +3385,10 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" - }, "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" } } }, @@ -4675,9 +4669,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "html-encoding-sniffer": { @@ -6356,6 +6350,11 @@ "supports-color": "^7.0.0" } }, + "js-graph-algorithms": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/js-graph-algorithms/-/js-graph-algorithms-1.0.18.tgz", + "integrity": "sha1-+W7Ie/GU9cCjE2X6Dh0Ht7li2JE=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6619,9 +6618,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.get": { "version": "4.4.2", @@ -6695,6 +6694,51 @@ "chalk": "^4.0.0" } }, + "looks-same": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/looks-same/-/looks-same-7.3.0.tgz", + "integrity": "sha512-pOfwX2d0frSt7H1cuBjDbw9Kry5QwkrFri0qJvLwV1sI0cbWkwYkpd7fF7SqSIfYKAZhgeB8PM3fyhUYz7xgqA==", + "requires": { + "color-diff": "^1.1.0", + "concat-stream": "^1.6.2", + "fs-extra": "^8.1.0", + "js-graph-algorithms": "1.0.18", + "lodash": "^4.17.3", + "nested-error-stacks": "^2.1.0", + "parse-color": "^1.0.0", + "pngjs": "^6.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6981,6 +7025,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7040,12 +7089,6 @@ } } }, - "node-releases": { - "version": "1.1.67", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", - "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==", - "dev": true - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -7090,11 +7133,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -7312,6 +7350,21 @@ "callsites": "^3.0.0" } }, + "parse-color": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz", + "integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=", + "requires": { + "color-convert": "~0.5.0" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, "parse-json": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", @@ -7331,20 +7384,14 @@ "dev": true }, "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "~1.0.0" - } + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" }, "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "~1.0.0" - } + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" }, "parseurl": { "version": "1.3.3", @@ -8591,15 +8638,15 @@ } }, "socket.io": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", - "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", + "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", "requires": { "debug": "~4.1.0", - "engine.io": "~3.4.0", + "engine.io": "~3.5.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.3.0", + "socket.io-client": "2.4.0", "socket.io-parser": "~3.4.0" }, "dependencies": { @@ -8619,37 +8666,34 @@ "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" }, "socket.io-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", - "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", + "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", "requires": { "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "engine.io-client": "~3.4.0", + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "engine.io-client": "~3.5.0", "has-binary2": "~1.0.2", - "has-cors": "1.1.0", "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", + "parseqs": "0.0.6", + "parseuri": "0.0.6", "socket.io-parser": "~3.3.0", "to-array": "0.1.4" }, "dependencies": { - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, "isarray": { @@ -8657,34 +8701,19 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "socket.io-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz", - "integrity": "sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", + "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", "requires": { "component-emitter": "~1.3.0", "debug": "~3.1.0", "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } } } @@ -9666,9 +9695,9 @@ "dev": true }, "underscore": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.0.tgz", - "integrity": "sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" }, "union-value": { "version": "1.0.1", @@ -10066,7 +10095,8 @@ "ws": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "dev": true }, "xml-name-validator": { "version": "3.0.0", @@ -10081,9 +10111,9 @@ "dev": true }, "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz", + "integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==" }, "xtend": { "version": "4.0.2", diff --git a/package.json b/package.json index 2d62494f..7a5bd610 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "@nestjs/jwt": "^7.1.0", "@nestjs/passport": "^7.1.0", "@nestjs/platform-express": "^7.4.2", - "@nestjs/platform-socket.io": "^7.4.2", - "@nestjs/swagger": "^4.5.12", + "@nestjs/platform-socket.io": "^7.6.17", + "@nestjs/swagger": "^4.8.0", "@nestjs/websockets": "^7.4.2", "@prisma/client": "2.12.1", "bcryptjs": "^2.4.3", @@ -36,6 +36,7 @@ "class-transformer": "^0.3.1", "class-validator": "^0.12.2", "fs-extra": "^9.0.1", + "looks-same": "^7.3.0", "passport": "^0.4.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", From c1ae61e9a19aaaf17207d4f211648b44aa18c4fc Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 22:50:46 +0300 Subject: [PATCH 05/10] service added --- src/compare/compare.module.ts | 5 +- src/compare/compare.service.ts | 27 +++-- src/compare/libs/ImageCompareInput.ts | 10 ++ .../libs/image-comparator.interface.ts | 21 +--- .../libs/looks-same/looks-same.service.ts | 106 ++++++++++++++++++ .../libs/looks-same/looks-same.types.ts | 60 ++++++++++ 6 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 src/compare/libs/ImageCompareInput.ts create mode 100644 src/compare/libs/looks-same/looks-same.service.ts create mode 100644 src/compare/libs/looks-same/looks-same.types.ts diff --git a/src/compare/compare.module.ts b/src/compare/compare.module.ts index 10f95206..d3a0879d 100644 --- a/src/compare/compare.module.ts +++ b/src/compare/compare.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { CompareService } from './compare.service'; -import { PixelmatchService } from './libs/pixelmatch.service'; +import { LookSameService } from './libs/looks-same/looks-same.service'; +import { PixelmatchService } from './libs/pixelmatch/pixelmatch.service'; @Module({ - providers: [CompareService, PixelmatchService], + providers: [CompareService, PixelmatchService, LookSameService], exports: [CompareService], }) export class CompareModule {} diff --git a/src/compare/compare.service.ts b/src/compare/compare.service.ts index 1f062850..3d369670 100644 --- a/src/compare/compare.service.ts +++ b/src/compare/compare.service.ts @@ -1,17 +1,25 @@ import { ImageComparison, Project } from '.prisma/client'; import { Injectable } from '@nestjs/common'; -import { PixelmatchService } from './libs/pixelmatch.service'; -import { ImageComparator, ImageCompareConfig, ImageCompareInput } from './libs/image-comparator.interface'; +import { PixelmatchService } from './libs/pixelmatch/pixelmatch.service'; +import { ImageComparator } from './libs/image-comparator.interface'; +import { ImageCompareInput } from "./libs/ImageCompareInput"; import { PrismaService } from '../prisma/prisma.service'; import { DiffResult } from '../test-runs/diffResult'; +import { LookSameService } from './libs/looks-same/looks-same.service'; @Injectable() export class CompareService { - constructor(private pixelmatchService: PixelmatchService, private prismaService: PrismaService) {} + constructor( + private pixelmatchService: PixelmatchService, + private lookSameService: LookSameService, + private prismaService: PrismaService + ) {} async getDiff({ projectId, data }: { projectId: string; data: ImageCompareInput }): Promise { const project: Project = await this.prismaService.project.findUnique({ where: { id: projectId } }); - return this.getComparator(project.imageComparison).getDiff(data, this.getConfig(project)); + const comparator = this.getComparator(project.imageComparison); + const config = comparator.parseConfig(project.imageComparisonConfig); + return comparator.getDiff(data, config); } getComparator(imageComparison: ImageComparison): ImageComparator { @@ -19,17 +27,12 @@ export class CompareService { case ImageComparison.pixelmatch: { return this.pixelmatchService; } + case ImageComparison.lookSame: { + return this.lookSameService; + } default: { return this.pixelmatchService; } } } - - getConfig(project: Project): ImageCompareConfig { - return { - allowDiffDimensions: project.diffDimensionsFeature, - ignoreAntialiasing: project.ignoreAntialiasing, - threshold: project.threshold, - }; - } } diff --git a/src/compare/libs/ImageCompareInput.ts b/src/compare/libs/ImageCompareInput.ts new file mode 100644 index 00000000..8391beaf --- /dev/null +++ b/src/compare/libs/ImageCompareInput.ts @@ -0,0 +1,10 @@ +import { IgnoreAreaDto } from 'src/test-runs/dto/ignore-area.dto'; + + +export interface ImageCompareInput { + baseline: string; + image: string; + diffTollerancePercent: number; + ignoreAreas: IgnoreAreaDto[]; + saveDiffAsFile: boolean; +} diff --git a/src/compare/libs/image-comparator.interface.ts b/src/compare/libs/image-comparator.interface.ts index 35f78687..1b04aaa2 100644 --- a/src/compare/libs/image-comparator.interface.ts +++ b/src/compare/libs/image-comparator.interface.ts @@ -1,20 +1,9 @@ import { DiffResult } from 'src/test-runs/diffResult'; -import { IgnoreAreaDto } from 'src/test-runs/dto/ignore-area.dto'; - -export interface ImageCompareInput { - baseline: string; - image: string; - diffTollerancePercent: number; - ignoreAreas: IgnoreAreaDto[]; - saveDiffAsFile: boolean; -} - -export interface ImageCompareConfig { - allowDiffDimensions: boolean; - ignoreAntialiasing: boolean; - threshold: number; -} +import { ImageCompareInput } from './ImageCompareInput'; +import { LooksSameConfig } from './looks-same/looks-same.types'; +import { PixelmatchConfig } from './pixelmatch/pixelmatch.types'; export interface ImageComparator { - getDiff(data: ImageCompareInput, config: ImageCompareConfig): DiffResult; + getDiff(data: ImageCompareInput, config: PixelmatchConfig | LooksSameConfig): Promise; + parseConfig(configJson: string): PixelmatchConfig | LooksSameConfig; } diff --git a/src/compare/libs/looks-same/looks-same.service.ts b/src/compare/libs/looks-same/looks-same.service.ts new file mode 100644 index 00000000..9daa08ab --- /dev/null +++ b/src/compare/libs/looks-same/looks-same.service.ts @@ -0,0 +1,106 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { TestStatus } from '@prisma/client'; +import { PNG } from 'pngjs'; +import { StaticService } from '../../../shared/static/static.service'; +import { DiffResult } from '../../../test-runs/diffResult'; +import { applyIgnoreAreas } from '../../utils'; +import { ImageComparator } from '../image-comparator.interface'; +import { ImageCompareInput } from '../ImageCompareInput'; +import { LookSameResult, LooksSameConfig } from './looks-same.types'; +import looksSame from 'looks-same'; + +@Injectable() +export class LookSameService implements ImageComparator { + private readonly logger: Logger = new Logger(LookSameService.name); + + constructor(private staticService: StaticService) {} + + parseConfig(configJson: string): LooksSameConfig { + try { + return JSON.parse(configJson); + } catch (ex) { + this.logger.error('Cannot parse config, fallback to default one ' + ex); + } + } + + async getDiff(data: ImageCompareInput, config: LooksSameConfig): Promise { + let result: DiffResult = { + status: undefined, + diffName: null, + pixelMisMatchCount: undefined, + diffPercent: undefined, + isSameDimension: undefined, + }; + + const baseline = this.staticService.getImage(data.baseline); + const image = this.staticService.getImage(data.image); + + if (!baseline) { + this.logger.log('No baseline'); + return result; + } + + result.isSameDimension = baseline.width === image.width && baseline.height === image.height; + + if (baseline.data.equals(image.data)) { + // equal images + result.status = TestStatus.ok; + return result; + } + + // apply ignore areas + const baselineIgnored = applyIgnoreAreas(baseline, data.ignoreAreas); + const imageIgnored = applyIgnoreAreas(image, data.ignoreAreas); + + // compare + const compareResult: LookSameResult = await this.compare(baselineIgnored, imageIgnored, config); + if (compareResult.equal) { + result.status = TestStatus.ok; + } else { + result.status = TestStatus.unresolved; + if (data.saveDiffAsFile) { + result.diffName = await this.createDiff(baselineIgnored, imageIgnored, config); + } + } + + return result; + } + + async compare(baseline: PNG, image: PNG, config: LooksSameConfig): Promise { + return new Promise((resolve, reject) => { + looksSame( + PNG.sync.write(baseline), + PNG.sync.write(image), + config, + (error: Error | null, diffResult: LookSameResult) => { + if (error) { + this.logger.error(error.message); + reject(error); + } + resolve(diffResult); + } + ); + }); + } + + async createDiff(baseline: PNG, image: PNG, config: LooksSameConfig): Promise { + return new Promise((resolve, reject) => { + looksSame.createDiff( + { + reference: PNG.sync.write(baseline), + current: PNG.sync.write(image), + highlightColor: '#ff00ff', + ...config, + }, + (error: Error | null, buffer: Buffer) => { + if (error) { + this.logger.error(error.message); + reject(error); + } + const diffName = this.staticService.saveImage('diff', buffer); + resolve(diffName); + } + ); + }); + } +} diff --git a/src/compare/libs/looks-same/looks-same.types.ts b/src/compare/libs/looks-same/looks-same.types.ts new file mode 100644 index 00000000..e25751fc --- /dev/null +++ b/src/compare/libs/looks-same/looks-same.types.ts @@ -0,0 +1,60 @@ +export interface LooksSameConfig { + /** + * strict comparsion + */ + strict?: boolean; + /** + * ΔE value that will be treated as error in non-strict mode + */ + tolerance?: number; + /** + * makes the search algorithm of the antialiasing less strict + */ + antialiasingTolerance?: number; + /** + * Ability to ignore antialiasing + */ + ignoreAntialiasing?: boolean; + /** + * Ability to ignore text caret + */ + ignoreCaret?: boolean; + allowDiffDimensions?: boolean; +} + +/** + * coordinate bounds + */ +export interface CoordBounds { + /** + * X-coordinate of upper left corner + */ + left: number; + /** + * Y-coordinate of upper left corner + */ + top: number; + /** + * X-coordinate of bottom right corner + */ + right: number; + /** + * Y-coordinate of bottom right corner + */ + bottom: number; +} + +export type LookSameResult = { + /** + * true if images are equal, false - otherwise + */ + equal?: boolean; + /** + * diff bounds for not equal images + */ + diffBounds?: CoordBounds; + /** + * diff clusters for not equal images + */ + diffClusters?: CoordBounds[]; +}; From 0030ed099e05cc9d0fa797c31a64eaceedb0a48f Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 22:55:55 +0300 Subject: [PATCH 06/10] api updated --- src/_data_/index.ts | 4 +--- src/projects/dto/project.dto.ts | 14 +++----------- src/projects/projects.service.ts | 8 ++------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/_data_/index.ts b/src/_data_/index.ts index 453f7ce3..aae12634 100644 --- a/src/_data_/index.ts +++ b/src/_data_/index.ts @@ -8,9 +8,7 @@ export const TEST_PROJECT: Project = { createdAt: new Date(), updatedAt: new Date(), autoApproveFeature: true, - diffDimensionsFeature: true, - ignoreAntialiasing: true, - threshold: 0.1, + imageComparisonConfig: '', imageComparison: ImageComparison.pixelmatch, }; diff --git a/src/projects/dto/project.dto.ts b/src/projects/dto/project.dto.ts index 6a2418ee..d2585a6e 100644 --- a/src/projects/dto/project.dto.ts +++ b/src/projects/dto/project.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsUUID, IsString, IsNumber, IsBoolean, IsEnum } from 'class-validator'; +import { IsUUID, IsString, IsNumber, IsBoolean, IsEnum, IsJSON } from 'class-validator'; import { ImageComparison, Project } from '@prisma/client'; export class ProjectDto implements Project { @@ -29,19 +29,11 @@ export class ProjectDto implements Project { @IsBoolean() autoApproveFeature: boolean; - @ApiProperty() - @IsBoolean() - diffDimensionsFeature: boolean; - @ApiProperty() @IsEnum(ImageComparison) imageComparison: ImageComparison; @ApiProperty() - @IsBoolean() - ignoreAntialiasing: boolean; - - @ApiProperty() - @IsNumber() - threshold: number; + @IsJSON() + imageComparisonConfig: string; } diff --git a/src/projects/projects.service.ts b/src/projects/projects.service.ts index e10f45ff..63e9663e 100644 --- a/src/projects/projects.service.ts +++ b/src/projects/projects.service.ts @@ -42,10 +42,8 @@ export class ProjectsService { name: projectDto.name, mainBranchName: projectDto.mainBranchName, autoApproveFeature: projectDto.autoApproveFeature, - diffDimensionsFeature: projectDto.diffDimensionsFeature, - ignoreAntialiasing: projectDto.ignoreAntialiasing, imageComparison: projectDto.imageComparison, - threshold: projectDto.threshold, + imageComparisonConfig: projectDto.imageComparisonConfig, }, }); } @@ -57,10 +55,8 @@ export class ProjectsService { name: projectDto.name, mainBranchName: projectDto.mainBranchName, autoApproveFeature: projectDto.autoApproveFeature, - diffDimensionsFeature: projectDto.diffDimensionsFeature, - ignoreAntialiasing: projectDto.ignoreAntialiasing, imageComparison: projectDto.imageComparison, - threshold: projectDto.threshold, + imageComparisonConfig: projectDto.imageComparisonConfig, }, }); } From 6f1dd5c87afd38a23a0eeb80aa64b310f4d74108 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 23:08:22 +0300 Subject: [PATCH 07/10] get default config updated --- src/_data_/index.ts | 2 +- src/compare/libs/pixelmatch/pixelmatch.service.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/_data_/index.ts b/src/_data_/index.ts index aae12634..b5fb72cd 100644 --- a/src/_data_/index.ts +++ b/src/_data_/index.ts @@ -8,7 +8,7 @@ export const TEST_PROJECT: Project = { createdAt: new Date(), updatedAt: new Date(), autoApproveFeature: true, - imageComparisonConfig: '', + imageComparisonConfig: '{ "threshold": 0.1, "ignoreAntialiasing": true, "allowDiffDimensions": false }', imageComparison: ImageComparison.pixelmatch, }; diff --git a/src/compare/libs/pixelmatch/pixelmatch.service.ts b/src/compare/libs/pixelmatch/pixelmatch.service.ts index e930051d..666c1b00 100644 --- a/src/compare/libs/pixelmatch/pixelmatch.service.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.ts @@ -6,8 +6,8 @@ import { StaticService } from '../../../shared/static/static.service'; import { DiffResult } from '../../../test-runs/diffResult'; import { scaleImageToSize, applyIgnoreAreas } from '../../utils'; import { ImageComparator } from '../image-comparator.interface'; -import { ImageCompareInput } from "../ImageCompareInput"; -import { PixelmatchConfig } from "./pixelmatch.types"; +import { ImageCompareInput } from '../ImageCompareInput'; +import { PixelmatchConfig } from './pixelmatch.types'; const DEFAULT_CONFIG: PixelmatchConfig = { threshold: 0.1, ignoreAntialiasing: true }; @@ -18,13 +18,12 @@ export class PixelmatchService implements ImageComparator { constructor(private staticService: StaticService) {} parseConfig(configJson: string): PixelmatchConfig { - let config: PixelmatchConfig = DEFAULT_CONFIG; try { - config = JSON.parse(configJson); + return JSON.parse(configJson) ?? DEFAULT_CONFIG; } catch (ex) { this.logger.error('Cannot parse config, fallback to default one ' + ex); } - return config; + return DEFAULT_CONFIG; } async getDiff(data: ImageCompareInput, config: PixelmatchConfig): Promise { From 763ed0285a164ef7edd130ecf1c8d390971d923d Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sun, 6 Jun 2021 23:29:04 +0300 Subject: [PATCH 08/10] break if diff dimension --- src/compare/libs/looks-same/looks-same.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compare/libs/looks-same/looks-same.service.ts b/src/compare/libs/looks-same/looks-same.service.ts index 9daa08ab..77948ad9 100644 --- a/src/compare/libs/looks-same/looks-same.service.ts +++ b/src/compare/libs/looks-same/looks-same.service.ts @@ -48,6 +48,12 @@ export class LookSameService implements ImageComparator { return result; } + if (!result.isSameDimension && !config.allowDiffDimensions) { + // diff dimensions + result.status = TestStatus.unresolved; + return result; + } + // apply ignore areas const baselineIgnored = applyIgnoreAreas(baseline, data.ignoreAreas); const imageIgnored = applyIgnoreAreas(image, data.ignoreAreas); From 22ea54f4a93b8d35e3a5e24dc606514a5e411a06 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Thu, 10 Jun 2021 09:15:10 +0300 Subject: [PATCH 09/10] test added --- .../looks-same/looks-same.service.spec.ts | 51 +++++++++++++++++++ .../libs/looks-same/looks-same.service.ts | 10 +++- .../pixelmatch/pixelmatch.service.spec.ts | 29 ++++++++++- .../libs/pixelmatch/pixelmatch.service.ts | 2 +- 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 src/compare/libs/looks-same/looks-same.service.spec.ts diff --git a/src/compare/libs/looks-same/looks-same.service.spec.ts b/src/compare/libs/looks-same/looks-same.service.spec.ts new file mode 100644 index 00000000..763c6196 --- /dev/null +++ b/src/compare/libs/looks-same/looks-same.service.spec.ts @@ -0,0 +1,51 @@ +import { TestingModule, Test } from '@nestjs/testing'; +import { TestStatus } from '@prisma/client'; +import { PNG } from 'pngjs'; +import { mocked } from 'ts-jest/utils'; +import { StaticService } from '../../../shared/static/static.service'; +import { DEFAULT_CONFIG, LookSameService } from './looks-same.service'; +import { LooksSameConfig } from './looks-same.types'; + +const initService = async ({ getImageMock = jest.fn(), saveImageMock = jest.fn(), deleteImageMock = jest.fn() }) => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + LookSameService, + { + provide: StaticService, + useValue: { + getImage: getImageMock, + saveImage: saveImageMock, + deleteImage: deleteImageMock, + }, + }, + ], + }).compile(); + + return module.get(LookSameService); +}; + +let service: LookSameService; + +describe('parseConfig', () => { + it.each<[string, LooksSameConfig]>([ + [ + '{"strict":false,"tolerance":122.1,"antialiasingTolerance":2,"ignoreAntialiasing":true,"ignoreCaret":true,"allowDiffDimensions":true}', + { + strict: false, + tolerance: 122.1, + antialiasingTolerance: 2, + ignoreAntialiasing: true, + ignoreCaret: true, + allowDiffDimensions: true, + }, + ], + ['', DEFAULT_CONFIG], + ['invalid', DEFAULT_CONFIG], + ])('should parse config', async (json, expected) => { + service = await initService({}); + + const config = service.parseConfig(json); + + expect(config).toStrictEqual(expected); + }); +}); diff --git a/src/compare/libs/looks-same/looks-same.service.ts b/src/compare/libs/looks-same/looks-same.service.ts index 77948ad9..112be2c7 100644 --- a/src/compare/libs/looks-same/looks-same.service.ts +++ b/src/compare/libs/looks-same/looks-same.service.ts @@ -9,6 +9,13 @@ import { ImageCompareInput } from '../ImageCompareInput'; import { LookSameResult, LooksSameConfig } from './looks-same.types'; import looksSame from 'looks-same'; +export const DEFAULT_CONFIG: LooksSameConfig = { + strict: false, + ignoreAntialiasing: true, + ignoreCaret: true, + allowDiffDimensions: false, +}; + @Injectable() export class LookSameService implements ImageComparator { private readonly logger: Logger = new Logger(LookSameService.name); @@ -17,10 +24,11 @@ export class LookSameService implements ImageComparator { parseConfig(configJson: string): LooksSameConfig { try { - return JSON.parse(configJson); + return JSON.parse(configJson) ?? DEFAULT_CONFIG; } catch (ex) { this.logger.error('Cannot parse config, fallback to default one ' + ex); } + return DEFAULT_CONFIG; } async getDiff(data: ImageCompareInput, config: LooksSameConfig): Promise { diff --git a/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts b/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts index 3b4673c8..09f19f8d 100644 --- a/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts @@ -4,7 +4,8 @@ import Pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; import { mocked } from 'ts-jest/utils'; import { StaticService } from '../../../shared/static/static.service'; -import { PixelmatchService } from './pixelmatch.service'; +import { DEFAULT_CONFIG, PixelmatchService } from './pixelmatch.service'; +import { PixelmatchConfig } from './pixelmatch.types'; jest.mock('pixelmatch'); @@ -26,8 +27,32 @@ const initService = async ({ getImageMock = jest.fn(), saveImageMock = jest.fn() return module.get(PixelmatchService); }; +let service: PixelmatchService; + +describe('parseConfig', () => { + it.each<[string, PixelmatchConfig]>([ + [ + "{\"threshold\":21.2,\"ignoreAntialiasing\":false,\"allowDiffDimensions\":true}", + { threshold: 21.2, ignoreAntialiasing: false, allowDiffDimensions: true }, + ], + [ + "", + DEFAULT_CONFIG, + ], + [ + "invalid", + DEFAULT_CONFIG, + ], + ])('should parse config', async (json, expected) => { + service = await initService({}); + + const config = service.parseConfig(json); + + expect(config).toStrictEqual(expected); + }); +}); + describe('getDiff', () => { - let service: PixelmatchService; const image = new PNG({ width: 20, height: 20, diff --git a/src/compare/libs/pixelmatch/pixelmatch.service.ts b/src/compare/libs/pixelmatch/pixelmatch.service.ts index 666c1b00..a5144045 100644 --- a/src/compare/libs/pixelmatch/pixelmatch.service.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.ts @@ -9,7 +9,7 @@ import { ImageComparator } from '../image-comparator.interface'; import { ImageCompareInput } from '../ImageCompareInput'; import { PixelmatchConfig } from './pixelmatch.types'; -const DEFAULT_CONFIG: PixelmatchConfig = { threshold: 0.1, ignoreAntialiasing: true }; +export const DEFAULT_CONFIG: PixelmatchConfig = { threshold: 0.1, ignoreAntialiasing: true }; @Injectable() export class PixelmatchService implements ImageComparator { From 68249efe1d47b94214b93b9339140862021d8111 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Thu, 10 Jun 2021 19:10:43 +0300 Subject: [PATCH 10/10] tests are added --- src/compare/libs/consts.ts | 24 +++ .../looks-same/looks-same.service.spec.ts | 150 ++++++++++++++++++ .../libs/looks-same/looks-same.service.ts | 21 +-- .../pixelmatch/pixelmatch.service.spec.ts | 69 ++------ .../libs/pixelmatch/pixelmatch.service.ts | 23 +-- 5 files changed, 199 insertions(+), 88 deletions(-) create mode 100644 src/compare/libs/consts.ts diff --git a/src/compare/libs/consts.ts b/src/compare/libs/consts.ts new file mode 100644 index 00000000..0026e212 --- /dev/null +++ b/src/compare/libs/consts.ts @@ -0,0 +1,24 @@ +import { TestStatus } from '@prisma/client'; +import { DiffResult } from 'src/test-runs/diffResult'; + +export const NO_BASELINE_RESULT: DiffResult = { + status: undefined, + diffName: null, + pixelMisMatchCount: undefined, + diffPercent: undefined, + isSameDimension: undefined, +}; + +export const EQUAL_RESULT: DiffResult = { + ...NO_BASELINE_RESULT, + status: TestStatus.ok, + pixelMisMatchCount: 0, + diffPercent: 0, + isSameDimension: true, +}; + +export const DIFF_DIMENSION_RESULT: DiffResult = { + ...NO_BASELINE_RESULT, + status: TestStatus.unresolved, + isSameDimension: false, +}; diff --git a/src/compare/libs/looks-same/looks-same.service.spec.ts b/src/compare/libs/looks-same/looks-same.service.spec.ts index 763c6196..e499c206 100644 --- a/src/compare/libs/looks-same/looks-same.service.spec.ts +++ b/src/compare/libs/looks-same/looks-same.service.spec.ts @@ -3,6 +3,7 @@ import { TestStatus } from '@prisma/client'; import { PNG } from 'pngjs'; import { mocked } from 'ts-jest/utils'; import { StaticService } from '../../../shared/static/static.service'; +import { DIFF_DIMENSION_RESULT, EQUAL_RESULT, NO_BASELINE_RESULT } from '../consts'; import { DEFAULT_CONFIG, LookSameService } from './looks-same.service'; import { LooksSameConfig } from './looks-same.types'; @@ -49,3 +50,152 @@ describe('parseConfig', () => { expect(config).toStrictEqual(expected); }); }); + +describe('getDiff', () => { + const image = new PNG({ + width: 20, + height: 20, + }); + const anotherImage = new PNG({ + width: 20, + height: 20, + }); + anotherImage.data[0] = 1; // alterate pixel to have it different from 0 + + it('no baseline', async () => { + const getImageMock = jest.fn().mockReturnValueOnce(undefined).mockReturnValueOnce(image); + service = await initService({ getImageMock }); + + const result = await service.getDiff( + { + baseline: null, + image: 'image', + diffTollerancePercent: 0.1, + ignoreAreas: [], + saveDiffAsFile: true, + }, + DEFAULT_CONFIG + ); + + expect(result).toStrictEqual(NO_BASELINE_RESULT); + }); + + it('equal images', async () => { + const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(image); + service = await initService({ getImageMock }); + + const result = await service.getDiff( + { + baseline: 'image', + image: 'image', + diffTollerancePercent: 0.1, + ignoreAreas: [], + saveDiffAsFile: true, + }, + DEFAULT_CONFIG + ); + + expect(result).toStrictEqual(EQUAL_RESULT); + }); + + it('diff image dimensions mismatch', async () => { + const baseline = new PNG({ + width: 10, + height: 10, + }); + const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(baseline); + service = await initService({ getImageMock }); + + const result = await service.getDiff( + { + baseline: 'image', + image: 'image', + diffTollerancePercent: 0.1, + ignoreAreas: [], + saveDiffAsFile: true, + }, + DEFAULT_CONFIG + ); + + expect(result).toStrictEqual(DIFF_DIMENSION_RESULT); + }); + + it('diff not found', async () => { + const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(anotherImage); + service = await initService({ getImageMock }); + service.compare = jest.fn().mockReturnValueOnce({ equal: true }); + + const result = await service.getDiff( + { + baseline: 'image', + image: 'image', + diffTollerancePercent: 0.1, + ignoreAreas: [], + saveDiffAsFile: true, + }, + DEFAULT_CONFIG + ); + + expect(result).toStrictEqual({ + status: TestStatus.ok, + diffName: null, + diffPercent: undefined, + pixelMisMatchCount: undefined, + isSameDimension: true, + }); + }); + + it('diff found', async () => { + const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(anotherImage); + service = await initService({ getImageMock }); + service.compare = jest.fn().mockReturnValueOnce({ equal: false }); + + const result = await service.getDiff( + { + baseline: 'image', + image: 'image', + diffTollerancePercent: 0.1, + ignoreAreas: [], + saveDiffAsFile: false, + }, + DEFAULT_CONFIG + ); + + expect(service.compare).toHaveBeenCalledWith(image, anotherImage, DEFAULT_CONFIG) + expect(result).toStrictEqual({ + status: TestStatus.unresolved, + diffName: null, + diffPercent: undefined, + pixelMisMatchCount: undefined, + isSameDimension: true, + }); + }); + + it('diff found and save diff', async () => { + const getImageMock = jest.fn().mockReturnValueOnce(image).mockReturnValueOnce(anotherImage); + service = await initService({ getImageMock }); + service.compare = jest.fn().mockReturnValueOnce({ equal: false }); + service.createDiff = jest.fn().mockReturnValueOnce('diff name'); + + const result = await service.getDiff( + { + baseline: 'image', + image: 'image', + diffTollerancePercent: 0.1, + ignoreAreas: [], + saveDiffAsFile: true, + }, + DEFAULT_CONFIG + ); + + expect(service.compare).toHaveBeenCalledWith(image, anotherImage, DEFAULT_CONFIG) + expect(service.createDiff).toHaveBeenCalledWith(image, anotherImage, DEFAULT_CONFIG) + expect(result).toStrictEqual({ + status: TestStatus.unresolved, + diffName: 'diff name', + diffPercent: undefined, + pixelMisMatchCount: undefined, + isSameDimension: true, + }); + }); +}); diff --git a/src/compare/libs/looks-same/looks-same.service.ts b/src/compare/libs/looks-same/looks-same.service.ts index 112be2c7..a06044db 100644 --- a/src/compare/libs/looks-same/looks-same.service.ts +++ b/src/compare/libs/looks-same/looks-same.service.ts @@ -8,6 +8,7 @@ import { ImageComparator } from '../image-comparator.interface'; import { ImageCompareInput } from '../ImageCompareInput'; import { LookSameResult, LooksSameConfig } from './looks-same.types'; import looksSame from 'looks-same'; +import { DIFF_DIMENSION_RESULT, EQUAL_RESULT, NO_BASELINE_RESULT } from '../consts'; export const DEFAULT_CONFIG: LooksSameConfig = { strict: false, @@ -33,33 +34,23 @@ export class LookSameService implements ImageComparator { async getDiff(data: ImageCompareInput, config: LooksSameConfig): Promise { let result: DiffResult = { - status: undefined, - diffName: null, - pixelMisMatchCount: undefined, - diffPercent: undefined, - isSameDimension: undefined, + ...NO_BASELINE_RESULT, }; const baseline = this.staticService.getImage(data.baseline); const image = this.staticService.getImage(data.image); if (!baseline) { - this.logger.log('No baseline'); - return result; + return NO_BASELINE_RESULT; } - result.isSameDimension = baseline.width === image.width && baseline.height === image.height; - if (baseline.data.equals(image.data)) { - // equal images - result.status = TestStatus.ok; - return result; + return EQUAL_RESULT; } + result.isSameDimension = baseline.width === image.width && baseline.height === image.height; if (!result.isSameDimension && !config.allowDiffDimensions) { - // diff dimensions - result.status = TestStatus.unresolved; - return result; + return DIFF_DIMENSION_RESULT; } // apply ignore areas diff --git a/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts b/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts index 09f19f8d..b139272c 100644 --- a/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.spec.ts @@ -4,6 +4,7 @@ import Pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; import { mocked } from 'ts-jest/utils'; import { StaticService } from '../../../shared/static/static.service'; +import { DIFF_DIMENSION_RESULT, EQUAL_RESULT, NO_BASELINE_RESULT } from '../consts'; import { DEFAULT_CONFIG, PixelmatchService } from './pixelmatch.service'; import { PixelmatchConfig } from './pixelmatch.types'; @@ -32,17 +33,11 @@ let service: PixelmatchService; describe('parseConfig', () => { it.each<[string, PixelmatchConfig]>([ [ - "{\"threshold\":21.2,\"ignoreAntialiasing\":false,\"allowDiffDimensions\":true}", + '{"threshold":21.2,"ignoreAntialiasing":false,"allowDiffDimensions":true}', { threshold: 21.2, ignoreAntialiasing: false, allowDiffDimensions: true }, ], - [ - "", - DEFAULT_CONFIG, - ], - [ - "invalid", - DEFAULT_CONFIG, - ], + ['', DEFAULT_CONFIG], + ['invalid', DEFAULT_CONFIG], ])('should parse config', async (json, expected) => { service = await initService({}); @@ -70,20 +65,10 @@ describe('getDiff', () => { ignoreAreas: [], saveDiffAsFile: true, }, - { - allowDiffDimensions: true, - ignoreAntialiasing: true, - threshold: 0, - } + DEFAULT_CONFIG ); - expect(result).toStrictEqual({ - status: undefined, - diffName: null, - pixelMisMatchCount: undefined, - diffPercent: undefined, - isSameDimension: undefined, - }); + expect(result).toStrictEqual(NO_BASELINE_RESULT); }); it('diff not found', async () => { @@ -98,20 +83,10 @@ describe('getDiff', () => { ignoreAreas: [], saveDiffAsFile: true, }, - { - allowDiffDimensions: true, - ignoreAntialiasing: true, - threshold: 0, - } + DEFAULT_CONFIG ); - expect(result).toStrictEqual({ - status: TestStatus.ok, - diffName: null, - pixelMisMatchCount: 0, - diffPercent: 0, - isSameDimension: true, - }); + expect(result).toStrictEqual(EQUAL_RESULT); }); it('diff image dimensions mismatch', async () => { @@ -130,20 +105,10 @@ describe('getDiff', () => { ignoreAreas: [], saveDiffAsFile: true, }, - { - allowDiffDimensions: false, - ignoreAntialiasing: true, - threshold: 0, - } + DEFAULT_CONFIG ); - expect(result).toStrictEqual({ - status: TestStatus.unresolved, - diffName: null, - pixelMisMatchCount: undefined, - diffPercent: undefined, - isSameDimension: false, - }); + expect(result).toStrictEqual(DIFF_DIMENSION_RESULT); }); it('diff image dimensions mismatch ALLOWED', async () => { @@ -228,13 +193,9 @@ describe('getDiff', () => { image: 'image', diffTollerancePercent: 2, ignoreAreas: [], - saveDiffAsFile: true, + saveDiffAsFile: false, }, - { - allowDiffDimensions: true, - ignoreAntialiasing: true, - threshold: 0.1, - } + DEFAULT_CONFIG ); expect(saveImageMock).toHaveBeenCalledTimes(0); @@ -275,11 +236,7 @@ describe('getDiff', () => { ignoreAreas: [], saveDiffAsFile: true, }, - { - allowDiffDimensions: true, - ignoreAntialiasing: true, - threshold: 0.1, - } + DEFAULT_CONFIG ); expect(saveImageMock).toHaveBeenCalledTimes(1); diff --git a/src/compare/libs/pixelmatch/pixelmatch.service.ts b/src/compare/libs/pixelmatch/pixelmatch.service.ts index a5144045..e8eb10d3 100644 --- a/src/compare/libs/pixelmatch/pixelmatch.service.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.service.ts @@ -5,6 +5,7 @@ import { PNG } from 'pngjs'; import { StaticService } from '../../../shared/static/static.service'; import { DiffResult } from '../../../test-runs/diffResult'; import { scaleImageToSize, applyIgnoreAreas } from '../../utils'; +import { DIFF_DIMENSION_RESULT, EQUAL_RESULT, NO_BASELINE_RESULT } from '../consts'; import { ImageComparator } from '../image-comparator.interface'; import { ImageCompareInput } from '../ImageCompareInput'; import { PixelmatchConfig } from './pixelmatch.types'; @@ -28,35 +29,23 @@ export class PixelmatchService implements ImageComparator { async getDiff(data: ImageCompareInput, config: PixelmatchConfig): Promise { const result: DiffResult = { - status: undefined, - diffName: null, - pixelMisMatchCount: undefined, - diffPercent: undefined, - isSameDimension: undefined, + ...NO_BASELINE_RESULT, }; const baseline = this.staticService.getImage(data.baseline); const image = this.staticService.getImage(data.image); if (!baseline) { - // no baseline - return result; + return NO_BASELINE_RESULT; } - result.isSameDimension = baseline.width === image.width && baseline.height === image.height; - if (baseline.data.equals(image.data)) { - // equal images - result.status = TestStatus.ok; - result.pixelMisMatchCount = 0; - result.diffPercent = 0; - return result; + return EQUAL_RESULT; } + result.isSameDimension = baseline.width === image.width && baseline.height === image.height; if (!result.isSameDimension && !config.allowDiffDimensions) { - // diff dimensions - result.status = TestStatus.unresolved; - return result; + return DIFF_DIMENSION_RESULT; } // scale image to max size