diff --git a/lib/index.ts b/lib/index.ts index 45f560e..44032d4 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,2 +1,3 @@ export * from "./visualRegressionTracker"; +export * from "./testRunResult"; export * from "./types"; diff --git a/lib/testRunResult.spec.ts b/lib/testRunResult.spec.ts new file mode 100644 index 0000000..230c01c --- /dev/null +++ b/lib/testRunResult.spec.ts @@ -0,0 +1,46 @@ +import TestRunResult from "./testRunResult"; +import { TestRunResponse, TestStatus } from "./types"; + +describe("TestRunResult", () => { + it("only required images", () => { + const testRunResponse: TestRunResponse = { + url: "url", + status: TestStatus.ok, + pixelMisMatchCount: 12, + diffPercent: 0.12, + diffTollerancePercent: 0, + id: "some id", + imageName: "imageName", + merge: false, + }; + + const result = new TestRunResult(testRunResponse, "http://localhost"); + + expect(result.testRunResponse).toBe(testRunResponse); + expect(result.imageUrl).toBe("http://localhost/imageName"); + expect(result.diffUrl).toBeUndefined(); + expect(result.baselineUrl).toBeUndefined(); + }); + + it("all image", () => { + const testRunResponse: TestRunResponse = { + url: "url", + status: TestStatus.ok, + pixelMisMatchCount: 12, + diffPercent: 0.12, + diffTollerancePercent: 0, + id: "some id", + imageName: "imageName", + diffName: "diffName", + baselineName: "baselineName", + merge: false, + }; + + const result = new TestRunResult(testRunResponse, "http://localhost"); + + expect(result.testRunResponse).toBe(testRunResponse); + expect(result.imageUrl).toBe("http://localhost/imageName"); + expect(result.diffUrl).toBe("http://localhost/diffName"); + expect(result.baselineUrl).toBe("http://localhost/baselineName"); + }); +}); diff --git a/lib/testRunResult.ts b/lib/testRunResult.ts new file mode 100644 index 0000000..df4fe1c --- /dev/null +++ b/lib/testRunResult.ts @@ -0,0 +1,19 @@ +import { TestRunResponse } from "./types"; + +export default class TestRunResult { + testRunResponse: TestRunResponse; + imageUrl: string; + diffUrl?: string; + baselineUrl?: string; + + constructor(testRunResponse: TestRunResponse, apiUrl: string) { + this.testRunResponse = testRunResponse; + this.imageUrl = apiUrl.concat("/").concat(testRunResponse.imageName); + this.diffUrl = + testRunResponse.diffName && + apiUrl.concat("/").concat(testRunResponse.diffName); + this.baselineUrl = + testRunResponse.baselineName && + apiUrl.concat("/").concat(testRunResponse.baselineName); + } +} diff --git a/lib/types/index.ts b/lib/types/index.ts index 023462d..cb37d9b 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -1,5 +1,5 @@ -export * from "./build"; export * from "./config"; export * from "./testRun"; -export * from "./testRunResult"; -export * from "./testRunStatus"; +export * from "./response/buildResponse"; +export * from "./response/testRunResponse"; +export * from "./testStatus"; diff --git a/lib/types/build.ts b/lib/types/response/buildResponse.ts similarity index 52% rename from lib/types/build.ts rename to lib/types/response/buildResponse.ts index e6f6638..2e972ab 100644 --- a/lib/types/build.ts +++ b/lib/types/response/buildResponse.ts @@ -1,4 +1,4 @@ -export interface Build { +export interface BuildResponse { id: string; projectId: string; } diff --git a/lib/types/response/testRunResponse.ts b/lib/types/response/testRunResponse.ts new file mode 100644 index 0000000..8af6b61 --- /dev/null +++ b/lib/types/response/testRunResponse.ts @@ -0,0 +1,14 @@ +import { TestStatus } from "../testStatus"; + +export interface TestRunResponse { + id: string; + imageName: string; + diffName?: string; + baselineName?: string; + diffPercent: number; + diffTollerancePercent?: number; + pixelMisMatchCount?: number; + status: TestStatus; + url: string; + merge: boolean; +} diff --git a/lib/types/testRunResult.ts b/lib/types/testRunResult.ts deleted file mode 100644 index fcf9272..0000000 --- a/lib/types/testRunResult.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TestRunStatus } from "./"; - -export interface TestRunResult { - url: string; - status: TestRunStatus; - pixelMisMatchCount: number; - diffPercent: number; - diffTollerancePercent: number; -} diff --git a/lib/types/testRunStatus.ts b/lib/types/testStatus.ts similarity index 67% rename from lib/types/testRunStatus.ts rename to lib/types/testStatus.ts index 1ff7d81..f1b610d 100644 --- a/lib/types/testRunStatus.ts +++ b/lib/types/testStatus.ts @@ -1,4 +1,4 @@ -export enum TestRunStatus { +export enum TestStatus { new = "new", ok = "ok", unresolved = "unresolved", diff --git a/lib/visualRegressionTracker.spec.ts b/lib/visualRegressionTracker.spec.ts index 7b14c55..91738e9 100644 --- a/lib/visualRegressionTracker.spec.ts +++ b/lib/visualRegressionTracker.spec.ts @@ -1,11 +1,21 @@ import { VisualRegressionTracker } from "./visualRegressionTracker"; -import { Config, Build, TestRun, TestRunResult, TestRunStatus } from "./types"; +import { + Config, + BuildResponse, + TestRun, + TestRunResponse, + TestStatus, +} from "./types"; import { mocked } from "ts-jest/utils"; +import TestRunResult from "./testRunResult"; import axios, { AxiosError, AxiosResponse } from "axios"; jest.mock("axios"); const mockedAxios = mocked(axios, true); +jest.mock("./testRunResult"); +const mockedTestRunResult = mocked(TestRunResult, true); + const axiosError404: AxiosError = { isAxiosError: true, config: {}, @@ -118,60 +128,31 @@ describe("VisualRegressionTracker", () => { }; it("should track success", async () => { - const testRunResult: TestRunResult = { + const testRunResponse: TestRunResponse = { url: "url", - status: TestRunStatus.ok, + status: TestStatus.ok, pixelMisMatchCount: 12, diffPercent: 0.12, diffTollerancePercent: 0, + id: "some id", + imageName: "imageName", + diffName: "diffName", + baselineName: "baselineName", + merge: false, }; - vrt["submitTestResult"] = jest.fn().mockResolvedValueOnce(testRunResult); + vrt["submitTestResult"] = jest + .fn() + .mockResolvedValueOnce(testRunResponse); + vrt["processTestRun"] = jest.fn(); await vrt.track(testRun); expect(vrt["submitTestResult"]).toHaveBeenCalledWith(testRun); - }); - - describe.each<[TestRunStatus.new | TestRunStatus.unresolved, string]>([ - [TestRunStatus.new, "No baseline: "], - [TestRunStatus.unresolved, "Difference found: "], - ])("should track error", (status, expectedMessage) => { - const testRunResultMock: TestRunResult = { - url: "http://foo.bar", - status: TestRunStatus.ok, - pixelMisMatchCount: 12, - diffPercent: 0.12, - diffTollerancePercent: 0, - }; - - beforeEach(() => { - testRunResultMock.status = status; - }); - - it(`disabled soft assert should throw exception if status ${status}`, async () => { - vrt["config"].enableSoftAssert = false; - vrt["submitTestResult"] = jest - .fn() - .mockResolvedValueOnce(testRunResultMock); - - await expect(vrt.track(testRun)).rejects.toThrowError( - new Error(expectedMessage.concat(testRunResultMock.url)) - ); - }); - - it(`enabled soft assert should log error if status ${status}`, async () => { - console.error = jest.fn(); - vrt["config"].enableSoftAssert = true; - vrt["submitTestResult"] = jest - .fn() - .mockResolvedValueOnce(testRunResultMock); - - await vrt.track(testRun); - - expect(console.error).toHaveBeenCalledWith( - expectedMessage.concat(testRunResultMock.url) - ); - }); + expect(vrt["processTestRun"]).toHaveBeenCalledWith(testRunResponse); + expect(mockedTestRunResult).toHaveBeenCalledWith( + testRunResponse, + "http://localhost:4200" + ); }); }); @@ -179,7 +160,7 @@ describe("VisualRegressionTracker", () => { test("should start build", async () => { const buildId = "1312"; const projectId = "asd"; - const build: Build = { + const build: BuildResponse = { id: buildId, projectId: projectId, }; @@ -261,12 +242,15 @@ describe("VisualRegressionTracker", () => { describe("submitTestResults", () => { it("should submit test run", async () => { - const testRunResult: TestRunResult = { + const testRunResponse: TestRunResponse = { url: "url", - status: TestRunStatus.unresolved, + status: TestStatus.unresolved, pixelMisMatchCount: 12, diffPercent: 0.12, diffTollerancePercent: 0, + id: "some id", + imageName: "imageName", + merge: false, }; const testRun: TestRun = { name: "name", @@ -280,11 +264,11 @@ describe("VisualRegressionTracker", () => { const projectId = "asd"; vrt["buildId"] = buildId; vrt["projectId"] = projectId; - mockedAxios.post.mockResolvedValueOnce({ data: testRunResult }); + mockedAxios.post.mockResolvedValueOnce({ data: testRunResponse }); const result = await vrt["submitTestResult"](testRun); - expect(result).toBe(testRunResult); + expect(result).toBe(testRunResponse); expect(mockedAxios.post).toHaveBeenCalledWith( `${config.apiUrl}/test-runs`, { @@ -337,7 +321,7 @@ describe("VisualRegressionTracker", () => { }); test("handleResponse", async () => { - const build: Build = { + const build: BuildResponse = { id: "id", projectId: "projectId", }; @@ -375,4 +359,43 @@ describe("VisualRegressionTracker", () => { ); }); }); + + describe.each<[TestStatus.new | TestStatus.unresolved, string]>([ + [TestStatus.new, "No baseline: "], + [TestStatus.unresolved, "Difference found: "], + ])("processTestRun", (status, expectedMessage) => { + const testRunResponse: TestRunResponse = { + url: "http://foo.bar", + status: TestStatus.ok, + pixelMisMatchCount: 12, + diffPercent: 0.12, + diffTollerancePercent: 0, + id: "some id", + imageName: "imageName", + merge: false, + }; + + beforeEach(() => { + testRunResponse.status = status; + }); + + it(`disabled soft assert should throw exception if status ${status}`, () => { + vrt["config"].enableSoftAssert = false; + + expect(() => vrt["processTestRun"](testRunResponse)).toThrowError( + new Error(expectedMessage.concat(testRunResponse.url)) + ); + }); + + it(`enabled soft assert should log error if status ${status}`, () => { + console.error = jest.fn(); + vrt["config"].enableSoftAssert = true; + + vrt["processTestRun"](testRunResponse); + + expect(console.error).toHaveBeenCalledWith( + expectedMessage.concat(testRunResponse.url) + ); + }); + }); }); diff --git a/lib/visualRegressionTracker.ts b/lib/visualRegressionTracker.ts index 4988e7e..852adc1 100644 --- a/lib/visualRegressionTracker.ts +++ b/lib/visualRegressionTracker.ts @@ -1,4 +1,11 @@ -import { Config, Build, TestRun, TestRunResult, TestRunStatus } from "./types"; +import { + Config, + BuildResponse, + TestRun, + TestRunResponse, + TestStatus, +} from "./types"; +import TestRunResult from "./testRunResult"; import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios"; export class VisualRegressionTracker { @@ -26,7 +33,7 @@ export class VisualRegressionTracker { project: this.config.project, }; - const build: Build = await axios + const build: BuildResponse = await axios .post(`${this.config.apiUrl}/builds`, data, this.axiosConfig) .then(this.handleResponse) .catch(this.handleException); @@ -50,7 +57,7 @@ export class VisualRegressionTracker { .catch(this.handleException); } - private async submitTestResult(test: TestRun): Promise { + private async submitTestResult(test: TestRun): Promise { if (!this.isStarted()) { throw new Error("Visual Regression Tracker has not been started"); } @@ -89,17 +96,24 @@ export class VisualRegressionTracker { } } - async track(test: TestRun) { - const result = await this.submitTestResult(test); + async track(test: TestRun): Promise { + const testRunResponse = await this.submitTestResult(test); + this.processTestRun(testRunResponse); + + return new TestRunResult(testRunResponse, this.config.apiUrl); + } + + private processTestRun(testRunResponse: TestRunResponse): void { let errorMessage: string | undefined; - switch (result.status) { - case TestRunStatus.new: { - errorMessage = `No baseline: ${result.url}`; + switch (testRunResponse.status) { + case TestStatus.new: { + errorMessage = `No baseline: ${testRunResponse.url}`; break; } - case TestRunStatus.unresolved: { - errorMessage = `Difference found: ${result.url}`; + case TestStatus.unresolved: { + errorMessage = `Difference found: ${testRunResponse.url}`; + break; } } diff --git a/package.json b/package.json index 91c66ad..310735f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@visual-regression-tracker/sdk-js", - "version": "4.2.0", + "version": "4.3.0", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts",