From 78e24e7af765aaa005c63ac2fcc2667e4760d1e6 Mon Sep 17 00:00:00 2001 From: jdtoombs Date: Thu, 12 Dec 2024 17:23:55 -0800 Subject: [PATCH] tests for utils --- .../constructReassessmentReduction.test.js | 138 ++++++++++++++++++ .../app/utilities/__tests__/download.test.js | 79 ++++++++++ .../utilities/__tests__/formatAddress.test.js | 55 +++++++ .../app/utilities/__tests__/upload.test.js | 101 +++++++++++++ 4 files changed, 373 insertions(+) create mode 100644 frontend/src/app/utilities/__tests__/constructReassessmentReduction.test.js create mode 100644 frontend/src/app/utilities/__tests__/download.test.js create mode 100644 frontend/src/app/utilities/__tests__/formatAddress.test.js create mode 100644 frontend/src/app/utilities/__tests__/upload.test.js diff --git a/frontend/src/app/utilities/__tests__/constructReassessmentReduction.test.js b/frontend/src/app/utilities/__tests__/constructReassessmentReduction.test.js new file mode 100644 index 000000000..c4552031d --- /dev/null +++ b/frontend/src/app/utilities/__tests__/constructReassessmentReduction.test.js @@ -0,0 +1,138 @@ +import constructReassessmentReductions from "../constructReassessmentReductions"; +import Big from "big.js"; + +describe("constructReassessmentReductions", () => { + it("should return empty reductions when both inputs are empty", () => { + const result = constructReassessmentReductions([], []); + expect(result).toEqual({ reductionsToUpdate: [], reductionsToAdd: [] }); + }); + + it("should add new reductions when no previous reductions exist", () => { + const reductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(10), + creditB: new Big(20), + }, + ]; + + const result = constructReassessmentReductions(reductions, []); + + expect(result).toEqual({ + reductionsToUpdate: [], + reductionsToAdd: [ + { creditClass: "A", modelYear: 2023, value: 10 }, + { creditClass: "B", modelYear: 2023, value: 20 }, + ], + }); + }); + + it("should be able to make changes in existing reductions", () => { + const reductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(15), + creditB: new Big(20), + }, + ]; + const prevReductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(10), + creditB: new Big(20), + }, + ]; + + const result = constructReassessmentReductions(reductions, prevReductions); + + expect(result).toEqual({ + reductionsToUpdate: [ + { + creditClass: "A", + modelYear: 2023, + oldValue: 10, + newValue: 15, + }, + ], + reductionsToAdd: [], + }); + }); + + it("should add reductions for new types or years not in previous reductions", () => { + const reductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(10), + creditB: new Big(20), + }, + { + type: "Type2", + modelYear: 2024, + creditA: new Big(5), + creditB: new Big(0), + }, + ]; + const prevReductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(10), + creditB: new Big(20), + }, + ]; + + const result = constructReassessmentReductions(reductions, prevReductions); + + expect(result).toEqual({ + reductionsToUpdate: [], + reductionsToAdd: [{ creditClass: "A", modelYear: 2024, value: 5 }], + }); + }); + + it("should return empty updates and additions when reductions match previous reductions", () => { + const reductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(10), + creditB: new Big(20), + }, + ]; + const prevReductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(10), + creditB: new Big(20), + }, + ]; + + const result = constructReassessmentReductions(reductions, prevReductions); + + expect(result).toEqual({ reductionsToUpdate: [], reductionsToAdd: [] }); + }); + + it("should handle reductions with no corresponding previous reductions", () => { + const reductions = [ + { + type: "Type1", + modelYear: 2023, + creditA: new Big(0), + creditB: new Big(30), + }, + ]; + + const prevReductions = []; + + const result = constructReassessmentReductions(reductions, prevReductions); + + expect(result).toEqual({ + reductionsToUpdate: [], + reductionsToAdd: [{ creditClass: "B", modelYear: 2023, value: 30 }], + }); + }); +}); diff --git a/frontend/src/app/utilities/__tests__/download.test.js b/frontend/src/app/utilities/__tests__/download.test.js new file mode 100644 index 000000000..c88adca4d --- /dev/null +++ b/frontend/src/app/utilities/__tests__/download.test.js @@ -0,0 +1,79 @@ +import download from "../download"; +import axios from "axios"; + +jest.mock("axios"); + +describe("download", () => { + const mockBlob = new Blob(["file content"], { type: "text/plain" }); + const mockSetAttribute = jest.fn(); + const mockClick = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + URL.createObjectURL = jest.fn(); + + document.createElement = jest.fn().mockImplementation((tagName) => { + if (tagName === "a") { + return { + setAttribute: mockSetAttribute, + click: mockClick, + href: "", + }; + } + return {}; + }); + document.body.appendChild = jest.fn(); + }); + + it("should download a file with the filename from content-disposition header", async () => { + const mockResponse = { + data: mockBlob, + headers: { "content-disposition": 'attachment; filename="testfile.txt"' }, + }; + axios.get.mockResolvedValue(mockResponse); + + await download("https://example.com/file", {}, null); + + expect(axios.get).toHaveBeenCalledWith("https://example.com/file", { + responseType: "blob", + }); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + expect(mockSetAttribute).toHaveBeenCalledWith("download", "testfile.txt"); + expect(mockClick).toHaveBeenCalledTimes(1); + }); + + it("should use the filename override if provided", async () => { + const mockResponse = { + data: mockBlob, + headers: {}, + }; + axios.get.mockResolvedValue(mockResponse); + + await download("https://example.com/file", {}, "customname.txt"); + + expect(axios.get).toHaveBeenCalledWith("https://example.com/file", { + responseType: "blob", + }); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + expect(mockSetAttribute).toHaveBeenCalledWith("download", "customname.txt"); + expect(mockClick).toHaveBeenCalledTimes(1); + }); + + it("should include additional params in the request", async () => { + const mockResponse = { + data: mockBlob, + headers: { "content-disposition": 'attachment; filename="testfile.txt"' }, + }; + + axios.get.mockResolvedValue(mockResponse); + + const params = { headers: { Authorization: "Bearer token" } }; + await download("https://example.com/file", params); + + expect(axios.get).toHaveBeenCalledWith("https://example.com/file", { + responseType: "blob", + ...params, + }); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockBlob); + }); +}); diff --git a/frontend/src/app/utilities/__tests__/formatAddress.test.js b/frontend/src/app/utilities/__tests__/formatAddress.test.js new file mode 100644 index 000000000..583995984 --- /dev/null +++ b/frontend/src/app/utilities/__tests__/formatAddress.test.js @@ -0,0 +1,55 @@ +import formatAddress from "../formatAddress"; + +describe("formatAddress", () => { + it("should return an empty string when no address is provided", () => { + expect(formatAddress(null)).toBe(""); + expect(formatAddress(undefined)).toBe(""); + }); + + it("should return the representative name when only representativeName is provided", () => { + const address = { representativeName: "John Doe" }; + expect(formatAddress(address)).toBe("John Doe"); + }); + + it("should only return address line when it is the only thing provided", () => { + const address = { addressLine1: "123 Main St" }; + expect(formatAddress(address)).toBe("123 Main St"); + }); + + it("should only return address line 2 when it is the only thing provided", () => { + const address = { addressLine2: "1233 Fake St" }; + expect(formatAddress(address)).toBe("1233 Fake St"); + }); + + it("should only return city when it is the only thing provided", () => { + const address = { city: "Vancouver" }; + expect(formatAddress(address)).toBe("Vancouver"); + }); + + it("should return a formatted address with multiple elements", () => { + const address = { + representativeName: "John Doe", + addressLine1: "123 Main St", + addressLine2: "Apt 4B", + city: "Victoria", + state: "BC", + country: "Canada", + postalCode: "V8V 3V3", + }; + const expected = + "John Doe, 123 Main St, Apt 4B, Victoria, BC, Canada, V8V 3V3"; + expect(formatAddress(address)).toBe(expected); + }); + + it("can handle missing elements", () => { + const address = { + representativeName: "John Doe", + addressLine1: "123 Main St", + city: "Victoria", + country: "Canada", + postalCode: "V8V 3V3", + }; + const expected = "John Doe, 123 Main St, Victoria, Canada, V8V 3V3"; + expect(formatAddress(address)).toBe(expected); + }); +}); diff --git a/frontend/src/app/utilities/__tests__/upload.test.js b/frontend/src/app/utilities/__tests__/upload.test.js new file mode 100644 index 000000000..e07b93bde --- /dev/null +++ b/frontend/src/app/utilities/__tests__/upload.test.js @@ -0,0 +1,101 @@ +import { upload, chunkUpload, getFileUploadPromises } from "../upload"; +import axios from "axios"; + +jest.mock("axios"); + +const mockFiles = [new File(["file content"], "testfile.txt")]; +const mockMultipleFiles = [ + new File(["file content"], "testfile.txt"), + new File(["file content"], "testfile2.txt"), +]; +const mockResponse = { + data: { success: true }, +}; +const mockUrl = "https://example.com/upload"; +const additionalData = { key1: "value1", key2: "value2" }; + +describe("upload", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should upload a file", async () => { + axios.post.mockResolvedValue(mockResponse); + + const result = await upload(mockUrl, mockFiles); + + expect(axios.post).toHaveBeenCalledWith(mockUrl, expect.any(FormData), { + headers: { "Content-Type": "multipart/form-data" }, + }); + expect(result).toEqual({ + data: expect.objectContaining({ success: true }), + }); + }); + + it("should upload multiple files", async () => { + axios.post.mockResolvedValue(mockResponse); + await upload(mockUrl, mockMultipleFiles); + expect(axios.post).toHaveBeenCalledWith(mockUrl, expect.any(FormData), { + headers: { "Content-Type": "multipart/form-data" }, + }); + const formData = axios.post.mock.calls[0][1]; + expect(formData.getAll("files").length).toBe(2); + }); + + it("should add additional data to the request", async () => { + axios.post.mockResolvedValue({ data: "success" }); + + const response = await upload(mockUrl, mockFiles, additionalData); + + expect(axios.post).toHaveBeenCalledWith( + mockUrl, + expect.any(FormData), + expect.objectContaining({ + headers: { "Content-Type": "multipart/form-data" }, + }), + ); + + const formData = axios.post.mock.calls[0][1]; + expect(formData.getAll("files").length).toBe(1); + expect(formData.getAll("files")[0]).toEqual(mockFiles[0]); + expect(formData.get("key1")).toBe("value1"); + expect(formData.get("key2")).toBe("value2"); + + expect(response).toEqual({ data: "success" }); + }); +}); + +describe("getFileUploadPromises", () => { + const mockPresignedUrlResponse = { + data: { + url: "http://example.com/upload", + minioObjectName: "mockObjectName", + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + jest.spyOn(global, "FileReader").mockImplementation(function () { + this.readAsArrayBuffer = jest.fn(() => { + this.onload({ target: { result: "mocked-file-content" } }); + }); + }); + + axios.get.mockResolvedValue(mockPresignedUrlResponse); + axios.put.mockResolvedValue({}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should return an array of promises", () => { + const promises = getFileUploadPromises(mockUrl, mockMultipleFiles); + + expect(promises).toHaveLength(mockMultipleFiles.length); + promises.forEach((promise) => { + expect(promise).toBeInstanceOf(Promise); + }); + }); +});