Browse Source

feat: Started adding unit and component tests to frontend with vitest

Owen Diffey 1 year ago
parent
commit
6f576fc8e7

+ 1 - 0
.gitignore

@@ -29,6 +29,7 @@ frontend/bundle-report.html
 frontend/node_modules/
 frontend/build/
 frontend/dist/config/default.json
+frontend/src/coverage/
 
 npm
 node_modules

+ 6 - 0
frontend/.eslintrc

@@ -43,6 +43,12 @@
 		"import/no-unresolved": 0,
 		"import/extensions": 0,
 		"import/prefer-default-export": 0,
+		"import/no-extraneous-dependencies": [
+			"error",
+			{
+				"devDependencies": true
+			}
+		],
 		"prettier/prettier": [
 			"error"
 		],

File diff suppressed because it is too large
+ 741 - 4
frontend/package-lock.json


+ 8 - 1
frontend/package.json

@@ -15,19 +15,26 @@
     "lint": "eslint --cache src --ext .js,.ts,.vue",
     "dev": "vite",
     "prod": "vite build --emptyOutDir",
-    "typescript": "vue-tsc --noEmit --skipLibCheck"
+    "typescript": "vue-tsc --noEmit --skipLibCheck",
+    "test": "vitest",
+    "coverage": "vitest run --coverage"
   },
   "devDependencies": {
+    "@pinia/testing": "^0.0.14",
     "@typescript-eslint/eslint-plugin": "^5.35.1",
     "@typescript-eslint/parser": "^5.35.1",
+    "@vitest/coverage-c8": "^0.22.1",
+    "@vue/test-utils": "^2.0.2",
     "eslint": "^8.22.0",
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^9.4.0",
+    "jsdom": "^20.0.0",
     "less": "^4.1.3",
     "prettier": "^2.7.1",
     "vite-plugin-dynamic-import": "^1.1.1",
+    "vitest": "^0.22.1",
     "vue-eslint-parser": "^9.0.3",
     "vue-tsc": "^0.39.5"
   },

+ 77 - 0
frontend/src/components/ChristmasLights.spec.ts

@@ -0,0 +1,77 @@
+import { mount, flushPromises } from "@vue/test-utils";
+import { createTestingPinia } from "@pinia/testing";
+import ChristmasLights from "@/components/ChristmasLights.vue";
+import { useUserAuthStore } from "@/stores/userAuth";
+
+describe("christmas lights component", () => {
+	test("mount", async () => {
+		expect(ChristmasLights).toBeTruthy();
+
+		const wrapper = mount(ChristmasLights, {
+			global: {
+				plugins: [createTestingPinia()]
+			}
+		});
+
+		expect(wrapper.classes()).toContain("christmas-lights");
+		expect(wrapper.html()).toMatchSnapshot();
+	});
+
+	test("props", async () => {
+		expect(ChristmasLights).toBeTruthy();
+
+		const wrapper = mount(ChristmasLights, {
+			global: {
+				plugins: [createTestingPinia()]
+			},
+			props: {
+				small: false,
+				lights: 1
+			}
+		});
+
+		expect(wrapper.classes()).not.toContain("christmas-lights-small");
+		expect(
+			wrapper.findAll(".christmas-lights .christmas-wire").length
+		).toBe(1 + 1);
+		expect(
+			wrapper.findAll(".christmas-lights .christmas-light").length
+		).toBe(1);
+
+		await wrapper.setProps({
+			small: true,
+			lights: 10
+		});
+		expect(wrapper.classes()).toContain("christmas-lights-small");
+		expect(
+			wrapper.findAll(".christmas-lights .christmas-wire").length
+		).toBe(10 + 1);
+		expect(
+			wrapper.findAll(".christmas-lights .christmas-light").length
+		).toBe(10);
+
+		expect(wrapper.html()).toMatchSnapshot();
+	});
+
+	test("loggedIn state", async () => {
+		expect(ChristmasLights).toBeTruthy();
+
+		const wrapper = mount(ChristmasLights, {
+			global: {
+				plugins: [createTestingPinia()]
+			}
+		});
+
+		const userAuthStore = useUserAuthStore();
+
+		expect(userAuthStore.loggedIn).toEqual(false);
+		expect(wrapper.classes()).not.toContain("loggedIn");
+
+		userAuthStore.loggedIn = true;
+		await flushPromises();
+		expect(userAuthStore.loggedIn).toEqual(true);
+		expect(wrapper.classes()).toContain("loggedIn");
+
+		expect(wrapper.html()).toMatchSnapshot();
+	});
+});

+ 68 - 0
frontend/src/components/InputHelpBox.spec.ts

@@ -0,0 +1,68 @@
+import { mount } from "@vue/test-utils";
+import InputHelpBox from "@/components/InputHelpBox.vue";
+
+test("input help box component props", async () => {
+	expect(InputHelpBox).toBeTruthy();
+
+	const wrapper = mount(InputHelpBox, {
+		props: {
+			message: "This input has not been entered and is valid.",
+			valid: true,
+			entered: false
+		}
+	});
+
+	expect(wrapper.text()).toBe(
+		"This input has not been entered and is valid."
+	);
+	expect(wrapper.classes()).toContain("is-grey");
+
+	await wrapper.setProps({
+		message: "This input has not been entered and is invalid.",
+		valid: false,
+		entered: false
+	});
+	expect(wrapper.text()).toBe(
+		"This input has not been entered and is invalid."
+	);
+	expect(wrapper.classes()).toContain("is-grey");
+
+	await wrapper.setProps({
+		message: "This input has been entered and is valid.",
+		valid: true,
+		entered: true
+	});
+	expect(wrapper.text()).toBe("This input has been entered and is valid.");
+	expect(wrapper.classes()).toContain("is-success");
+
+	await wrapper.setProps({
+		message: "This input has potentially been entered and is valid.",
+		valid: true,
+		entered: undefined
+	});
+	expect(wrapper.text()).toBe(
+		"This input has potentially been entered and is valid."
+	);
+	expect(wrapper.classes()).toContain("is-success");
+
+	await wrapper.setProps({
+		message: "This input has been entered and is invalid.",
+		valid: false,
+		entered: true
+	});
+	expect(wrapper.text()).toBe("This input has been entered and is invalid.");
+	expect(wrapper.classes()).toContain("is-danger");
+
+	await wrapper.setProps({
+		message: "This input has potentially been entered and is invalid.",
+		valid: false,
+		entered: undefined
+	});
+	expect(wrapper.text()).toBe(
+		"This input has potentially been entered and is invalid."
+	);
+	expect(wrapper.classes()).toContain("is-danger");
+
+	expect(wrapper.html()).toMatchSnapshot();
+	expect(wrapper.classes()).toContain("help");
+});

+ 31 - 0
frontend/src/components/__snapshots__/ChristmasLights.spec.ts.snap

@@ -0,0 +1,31 @@
+// Vitest Snapshot v1
+
+exports[`christmas lights component > loggedIn state 1`] = `
+"<div class=\\"christmas-lights loggedIn\\" data-v-1ff8e05a=\\"\\">
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div>
+</div>"
+`;
+
+exports[`christmas lights component > mount 1`] = `
+"<div class=\\"christmas-lights\\" data-v-1ff8e05a=\\"\\">
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div>
+</div>"
+`;
+
+exports[`christmas lights component > props 1`] = `
+"<div class=\\"christmas-lights christmas-lights-small\\" data-v-1ff8e05a=\\"\\">
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div><span class=\\"christmas-light\\" data-v-1ff8e05a=\\"\\"></span>
+  <div class=\\"christmas-wire\\" data-v-1ff8e05a=\\"\\"></div>
+</div>"
+`;

+ 3 - 0
frontend/src/components/__snapshots__/InputHelpBox.spec.ts.snap

@@ -0,0 +1,3 @@
+// Vitest Snapshot v1
+
+exports[`input help box component props 1`] = `"<p class=\\"help is-danger\\" data-v-109db604=\\"\\">This input has potentially been entered and is invalid.</p>"`;

+ 91 - 0
frontend/src/stores/longJobs.spec.ts

@@ -0,0 +1,91 @@
+import { setActivePinia, createPinia } from "pinia";
+import { useLongJobsStore } from "@/stores/longJobs";
+
+describe("long jobs store", () => {
+	beforeEach(() => {
+		setActivePinia(createPinia());
+	});
+
+	test("setJobs", () => {
+		const longJobsStore = useLongJobsStore();
+		const jobs = [
+			{
+				id: "f9c51c9b-2709-4c79-8263-998026fd8afb",
+				name: "Bulk verifying songs",
+				status: "success",
+				message: "2 songs have been successfully verified"
+			}
+		];
+		longJobsStore.setJobs(jobs);
+		expect(longJobsStore.activeJobs).toEqual(jobs);
+	});
+
+	test("setJob new", () => {
+		const longJobsStore = useLongJobsStore();
+		const job = {
+			id: "f9c51c9b-2709-4c79-8263-998026fd8afb",
+			name: "Bulk verifying songs",
+			status: "success",
+			message: "2 songs have been successfully verified"
+		};
+		longJobsStore.setJob(job);
+		expect(longJobsStore.activeJobs).toEqual([job]);
+	});
+
+	test("setJob update", () => {
+		const longJobsStore = useLongJobsStore();
+		longJobsStore.setJob({
+			id: "f9c51c9b-2709-4c79-8263-998026fd8afb",
+			name: "Bulk verifying songs",
+			status: "started",
+			message: "Verifying 2 songs.."
+		});
+		const updatedJob = {
+			id: "f9c51c9b-2709-4c79-8263-998026fd8afb",
+			name: "Bulk verifying songs",
+			status: "success",
+			message: "2 songs have been successfully verified"
+		};
+		longJobsStore.setJob(updatedJob);
+		expect(longJobsStore.activeJobs).toEqual([updatedJob]);
+	});
+
+	test("setJob already removed", () => {
+		const longJobsStore = useLongJobsStore();
+		const job = {
+			id: "f9c51c9b-2709-4c79-8263-998026fd8afb",
+			name: "Bulk verifying songs",
+			status: "success",
+			message: "2 songs have been successfully verified"
+		};
+		longJobsStore.setJob(job);
+		longJobsStore.removeJob("f9c51c9b-2709-4c79-8263-998026fd8afb");
+		longJobsStore.setJob(job);
+		expect(longJobsStore.activeJobs.length).toBe(0);
+		expect(longJobsStore.removedJobIds).toEqual([
+			"f9c51c9b-2709-4c79-8263-998026fd8afb"
+		]);
+	});
+
+	test("removeJob", () => {
+		const longJobsStore = useLongJobsStore();
+		longJobsStore.setJobs([
+			{
+				id: "f9c51c9b-2709-4c79-8263-998026fd8afb",
+				name: "Bulk verifying songs",
+				status: "success",
+				message: "2 songs have been successfully verified"
+			}
+		]);
+		longJobsStore.removeJob("f9c51c9b-2709-4c79-8263-998026fd8afb");
+		expect(longJobsStore.activeJobs.length).toBe(0);
+		expect(longJobsStore.removedJobIds).toContain(
+			"f9c51c9b-2709-4c79-8263-998026fd8afb"
+		);
+
+		longJobsStore.removeJob("e58fb1a6-14eb-4ce9-aed9-96c8afe17cbe");
+		expect(longJobsStore.removedJobIds).not.toContain(
+			"e58fb1a6-14eb-4ce9-aed9-96c8afe17cbe"
+		);
+	});
+});

+ 1 - 1
frontend/tsconfig.json

@@ -13,7 +13,7 @@
       ]
     },
     "jsx": "preserve",
-    "types": ["vite/client"]
+    "types": ["vite/client", "vitest/globals"]
   },
   "exclude": ["./src/index.html"]
 }

+ 8 - 0
frontend/vite.config.js

@@ -170,5 +170,13 @@ export default {
 	server,
 	build: {
 		outDir: "../build"
+	},
+	test: {
+		globals: true,
+		environment: "jsdom",
+		coverage: {
+			all: true,
+			extension: [".ts", ".vue"]
+		}
 	}
 };

+ 46 - 0
musare.sh

@@ -321,6 +321,52 @@ case $1 in
         fi
         ;;
 
+    test)
+        echo -e "${CYAN}Musare | Test${NC}"
+        servicesString=$(handleServices "frontend" "${@:2}")
+        if [[ ${servicesString:0:1} == 1 ]]; then
+            if [[ ${servicesString:2:4} == "all" || "${servicesString:2}" == *frontend* ]]; then
+                echo -e "${CYAN}Running frontend tests...${NC}"
+                ${dockerCompose} exec -T frontend npm run test -- --run
+                frontendExitValue=$?
+            fi
+            if [[ ${frontendExitValue} -gt 0 ]]; then
+                exitValue=1
+            else
+                exitValue=0
+            fi
+        else
+            echo -e "${RED}${servicesString:2}\n${YELLOW}Usage: $(basename "$0") test [frontend]${NC}"
+            exitValue=1
+        fi
+        if [[ ${exitValue} -gt 0 ]]; then
+            exit ${exitValue}
+        fi
+        ;;
+
+    test:coverage)
+        echo -e "${CYAN}Musare | Test Coverage${NC}"
+        servicesString=$(handleServices "frontend" "${@:2}")
+        if [[ ${servicesString:0:1} == 1 ]]; then
+            if [[ ${servicesString:2:4} == "all" || "${servicesString:2}" == *frontend* ]]; then
+                echo -e "${CYAN}Running frontend test coverage report...${NC}"
+                ${dockerCompose} exec -T frontend npm run coverage
+                frontendExitValue=$?
+            fi
+            if [[ ${frontendExitValue} -gt 0 ]]; then
+                exitValue=1
+            else
+                exitValue=0
+            fi
+        else
+            echo -e "${RED}${servicesString:2}\n${YELLOW}Usage: $(basename "$0") test:coverage [frontend]${NC}"
+            exitValue=1
+        fi
+        if [[ ${exitValue} -gt 0 ]]; then
+            exit ${exitValue}
+        fi
+        ;;
+
     update)
         echo -e "${CYAN}Musare | Update${NC}"
         git fetch

Some files were not shown because too many files changed in this diff