From 97381818ca7a7866384dc64eeae5607fe93df89e Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Tue, 16 Apr 2024 14:14:17 +0200
Subject: [PATCH 01/59] first implementation of component tests

---
 .../src/lib/components/CourseOverview.svelte  |   4 +
 .../test/{ => components}/Breadcrumb.test.js  |   8 +-
 frontend/test/components/CourseCards.test.js  |  29 +++
 .../test/components/CourseOverview.test.js    | 167 ++++++++++++++++++
 .../components/CourseOverviewLecturer.test.js | 167 ++++++++++++++++++
 5 files changed, 371 insertions(+), 4 deletions(-)
 rename frontend/test/{ => components}/Breadcrumb.test.js (80%)
 create mode 100644 frontend/test/components/CourseCards.test.js
 create mode 100644 frontend/test/components/CourseOverview.test.js
 create mode 100644 frontend/test/components/CourseOverviewLecturer.test.js

diff --git a/frontend/src/lib/components/CourseOverview.svelte b/frontend/src/lib/components/CourseOverview.svelte
index baea5cf..be2b01d 100644
--- a/frontend/src/lib/components/CourseOverview.svelte
+++ b/frontend/src/lib/components/CourseOverview.svelte
@@ -13,6 +13,10 @@
 		unit.unit_number = unitCounter;
 		unitCounter++;
 	});
+
+	console.log(data);
+	console.log(role);
+	console.log(units);
 </script>
 
 <main class="flex-shrink-0">
diff --git a/frontend/test/Breadcrumb.test.js b/frontend/test/components/Breadcrumb.test.js
similarity index 80%
rename from frontend/test/Breadcrumb.test.js
rename to frontend/test/components/Breadcrumb.test.js
index c2eefea..6421691 100644
--- a/frontend/test/Breadcrumb.test.js
+++ b/frontend/test/components/Breadcrumb.test.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
-import Breadcrumb from '../src/lib/components/Breadcrumb.svelte';
+import Breadcrumb from '../../src/lib/components/Breadcrumb.svelte';
 
 describe('BreadcrumbComponent', () => {
 	test('renders the home breadcrumb and additional breadcrumb items', () => {
@@ -11,14 +11,14 @@ describe('BreadcrumbComponent', () => {
 
 		render(Breadcrumb, { breadcrumbItems });
 
-		// Sjekk for hjem-breadcrumb
+		//Check for  home-breadcrumb
 		expect(screen.getByText('Courses')).toBeInTheDocument();
 
-		// Sjekk for tilleggs-breadcrumbs
+		//Check for additional-breadcrumbs
 		expect(screen.getByText('Course 1')).toBeInTheDocument();
 		expect(screen.getByText('Course 2')).toBeInTheDocument();
 
-		// Sjekk at href-verdiene er riktige
+		//Check for correct href attributes
 		expect(screen.getByText('Course 1').closest('a')).toHaveAttribute('href', '/course-1');
 		expect(screen.getByText('Course 2').closest('a')).toHaveAttribute('href', '/course-2');
 	});
diff --git a/frontend/test/components/CourseCards.test.js b/frontend/test/components/CourseCards.test.js
new file mode 100644
index 0000000..e84348a
--- /dev/null
+++ b/frontend/test/components/CourseCards.test.js
@@ -0,0 +1,29 @@
+/* eslint-disable no-undef */
+import { render, screen } from '@testing-library/svelte';
+import CourseCards from '../../src/lib/components/CourseCards.svelte';
+
+describe('CourseCardsComponent', () => {
+	test('renders the course card component correctly', () => {
+		const mockCourse = [
+			[
+				{
+					course_id: 'TDT0000',
+					course_semester: 'spring2024',
+					role: 'student',
+					uid: 'test',
+					missingUnits: []
+				}
+			]
+		];
+		render(CourseCards, { courses: mockCourse, role: 'student' });
+
+		//Check base render of course card, with correct course id
+		expect(screen.getByText('TDT0000')).toBeInTheDocument();
+
+		//Check that the course card displays correct semester
+		expect(screen.getByText('Spring 2024')).toBeInTheDocument();
+
+		//Check that the course card displays correct badge (student/lecturer)
+		expect(screen.getByText('Student')).toBeInTheDocument();
+	});
+});
diff --git a/frontend/test/components/CourseOverview.test.js b/frontend/test/components/CourseOverview.test.js
new file mode 100644
index 0000000..91101d6
--- /dev/null
+++ b/frontend/test/components/CourseOverview.test.js
@@ -0,0 +1,167 @@
+/* eslint-disable no-undef */
+import { render, screen } from '@testing-library/svelte';
+import CourseOverview from '../../src/lib/components/CourseOverview.svelte';
+
+describe('CourseOverviewComponent', () => {
+	test('renders the course overview component correctly for students', () => {
+		const mockData = [
+			{
+				user: {
+					uid: 'test',
+					email: 'test@ntnu.no',
+					enrollments: [
+						{
+							course_id: 'TDT4000',
+							course_semester: 'spring2024',
+							role: 'lecturer',
+							uid: 'test',
+							missingUnits: []
+						},
+						{
+							course_id: 'TDT4100',
+							course_semester: 'fall2023',
+							role: 'student',
+							uid: 'test',
+							missingUnits: [
+								{
+									id: 1,
+									date: '2022-08-23'
+								},
+								{
+									id: 2,
+									date: '2022-08-30'
+								}
+							]
+						}
+					],
+					reflections: [],
+					admin: true
+				},
+				course: {
+					id: 'TDT4100',
+					semester: 'fall2023',
+					name: 'Informasjonsteknologi grunnkurs',
+					responsible: '',
+					website: '',
+					questions: [
+						{
+							id: 1,
+							question: 'Teaching',
+							comment: 'What was your best learning success in this unit? Why?'
+						},
+						{
+							id: 2,
+							question: 'Difficult',
+							comment: 'What was your least understood concept in this unit? Why?'
+						}
+					],
+					users: [
+						{
+							course_id: 'TDT4100',
+							course_semester: 'fall2023',
+							role: 'student'
+						}
+					],
+					reports: [
+						{
+							report_content: [],
+							number_of_answers: 0,
+							unit_id: 1,
+							course_id: 'TDT4100',
+							course_semester: 'fall2023'
+						},
+						{
+							report_content: [],
+							number_of_answers: 0,
+							unit_id: 2,
+							course_id: 'TDT4100',
+							course_semester: 'fall2023'
+						},
+						{
+							report_content: [],
+							number_of_answers: 0,
+							unit_id: 3,
+							course_id: 'TDT4100',
+							course_semester: 'fall2023'
+						}
+					]
+				},
+				course_name: 'TDT4100',
+				role: 'student',
+				units: [
+					{
+						hidden: false,
+						title: 'State Machines',
+						date_available: '2022-08-23',
+						course_id: 'TDT4100',
+						course_semester: 'fall2023',
+						id: 1,
+						reflections: [],
+						unit_number: 1
+					},
+					{
+						hidden: false,
+						title: 'HTTP og JSON',
+						date_available: '2022-08-30',
+						course_id: 'TDT4100',
+						course_semester: 'fall2023',
+						id: 2,
+						reflections: [],
+						unit_number: 2
+					},
+					{
+						hidden: false,
+						title: 'MQTT Chat',
+						date_available: '2024-09-07',
+						course_id: 'TDT4100',
+						course_semester: 'fall2023',
+						id: 3,
+						reflections: [],
+						unit_number: 3
+					}
+				]
+			}
+		];
+
+		const mockUnits = [
+			{
+				hidden: false,
+				title: 'State Machines',
+				date_available: '2022-08-23',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 1,
+				reflections: [],
+				unit_number: 1
+			},
+			{
+				hidden: false,
+				title: 'HTTP og JSON',
+				date_available: '2022-08-30',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 2,
+				reflections: [],
+				unit_number: 2
+			},
+			{
+				hidden: false,
+				title: 'MQTT Chat',
+				date_available: '2024-09-07',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 3,
+				reflections: [],
+				unit_number: 3
+			}
+		];
+		//Render as student
+		render(CourseOverview, { data: mockData, units: mockUnits, role: 'student' });
+
+		//Check base render course overview component, name is static for both lecturer and student
+		expect(screen.getByText('State Machines')).toBeInTheDocument();
+
+		//Confirms that the breadcrumb is present in courseoverview
+		expect(screen.getByText('Courses')).toBeInTheDocument();
+	});
+});
diff --git a/frontend/test/components/CourseOverviewLecturer.test.js b/frontend/test/components/CourseOverviewLecturer.test.js
new file mode 100644
index 0000000..2a54b61
--- /dev/null
+++ b/frontend/test/components/CourseOverviewLecturer.test.js
@@ -0,0 +1,167 @@
+/* eslint-disable no-undef */
+import { render, screen } from '@testing-library/svelte';
+import CourseOverviewLecturer from '../../src/lib/components/CourseOverviewLecturer.svelte';
+
+describe('CourseOverviewLecturer', () => {
+	test('renders the course overview component correctly for lecturers', () => {
+		const mockData = [
+			{
+				user: {
+					uid: 'test',
+					email: 'test@ntnu.no',
+					enrollments: [
+						{
+							course_id: 'TDT4000',
+							course_semester: 'spring2024',
+							role: 'lecturer',
+							uid: 'test',
+							missingUnits: []
+						},
+						{
+							course_id: 'TDT4100',
+							course_semester: 'fall2023',
+							role: 'student',
+							uid: 'test',
+							missingUnits: [
+								{
+									id: 1,
+									date: '2022-08-23'
+								},
+								{
+									id: 2,
+									date: '2022-08-30'
+								}
+							]
+						}
+					],
+					reflections: [],
+					admin: true
+				},
+				course: {
+					id: 'TDT4100',
+					semester: 'fall2023',
+					name: 'Informasjonsteknologi grunnkurs',
+					responsible: '',
+					website: '',
+					questions: [
+						{
+							id: 1,
+							question: 'Teaching',
+							comment: 'What was your best learning success in this unit? Why?'
+						},
+						{
+							id: 2,
+							question: 'Difficult',
+							comment: 'What was your least understood concept in this unit? Why?'
+						}
+					],
+					users: [
+						{
+							course_id: 'TDT4100',
+							course_semester: 'fall2023',
+							role: 'student'
+						}
+					],
+					reports: [
+						{
+							report_content: [],
+							number_of_answers: 0,
+							unit_id: 1,
+							course_id: 'TDT4100',
+							course_semester: 'fall2023'
+						},
+						{
+							report_content: [],
+							number_of_answers: 0,
+							unit_id: 2,
+							course_id: 'TDT4100',
+							course_semester: 'fall2023'
+						},
+						{
+							report_content: [],
+							number_of_answers: 0,
+							unit_id: 3,
+							course_id: 'TDT4100',
+							course_semester: 'fall2023'
+						}
+					]
+				},
+				course_name: 'TDT4100',
+				role: 'student',
+				units: [
+					{
+						hidden: false,
+						title: 'State Machines',
+						date_available: '2022-08-23',
+						course_id: 'TDT4100',
+						course_semester: 'fall2023',
+						id: 1,
+						reflections: [],
+						unit_number: 1
+					},
+					{
+						hidden: false,
+						title: 'HTTP og JSON',
+						date_available: '2022-08-30',
+						course_id: 'TDT4100',
+						course_semester: 'fall2023',
+						id: 2,
+						reflections: [],
+						unit_number: 2
+					},
+					{
+						hidden: false,
+						title: 'MQTT Chat',
+						date_available: '2024-09-07',
+						course_id: 'TDT4100',
+						course_semester: 'fall2023',
+						id: 3,
+						reflections: [],
+						unit_number: 3
+					}
+				]
+			}
+		];
+
+		const mockUnits = [
+			{
+				hidden: false,
+				title: 'State Machines',
+				date_available: '2022-08-23',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 1,
+				reflections: [],
+				unit_number: 1
+			},
+			{
+				hidden: false,
+				title: 'HTTP og JSON',
+				date_available: '2022-08-30',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 2,
+				reflections: [],
+				unit_number: 2
+			},
+			{
+				hidden: false,
+				title: 'MQTT Chat',
+				date_available: '2024-09-07',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 3,
+				reflections: [],
+				unit_number: 3
+			}
+		];
+		//Render as student
+		render(CourseOverviewLecturer, { data: mockData, units: mockUnits, role: 'lecturer' });
+
+		//Check that create new unit button is present, confirmning lecturer role
+		expect(screen.getByText('Create new unit')).toBeInTheDocument();
+
+		//Check that the view report button is rendered correctly
+		expect(screen.getByText('View report')).toBeInTheDocument();
+	});
+});
-- 
GitLab


From c71265cdec2856df2dfa5df7ab640c8f647a43d6 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:11:14 +0200
Subject: [PATCH 02/59] testing

---
 frontend/package-lock.json                    | 100 +++------------
 frontend/package.json                         |   1 +
 frontend/test/components/UnitOverview.test.js | 114 ++++++++++++++++++
 frontend/vitest.config.js                     |  27 +++--
 4 files changed, 152 insertions(+), 90 deletions(-)
 create mode 100644 frontend/test/components/UnitOverview.test.js

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 58e611e..9182ae8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
 			"dependencies": {
 				"@popperjs/core": "^2.11.8",
 				"@sveltejs/adapter-vercel": "^5.1.0",
+				"@testing-library/user-event": "^14.5.2",
 				"classnames": "^2.5.1",
 				"date-picker-svelte": "^2.11.0",
 				"dotenv": "^16.4.5",
@@ -90,7 +91,6 @@
 			"version": "7.23.5",
 			"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
 			"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
-			"dev": true,
 			"dependencies": {
 				"@babel/highlight": "^7.23.4",
 				"chalk": "^2.4.2"
@@ -304,7 +304,6 @@
 			"version": "7.22.20",
 			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
 			"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
-			"dev": true,
 			"engines": {
 				"node": ">=6.9.0"
 			}
@@ -340,7 +339,6 @@
 			"version": "7.23.4",
 			"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
 			"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
-			"dev": true,
 			"dependencies": {
 				"@babel/helper-validator-identifier": "^7.22.20",
 				"chalk": "^2.4.2",
@@ -2550,7 +2548,6 @@
 			"version": "9.3.4",
 			"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
 			"integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==",
-			"dev": true,
 			"dependencies": {
 				"@babel/code-frame": "^7.10.4",
 				"@babel/runtime": "^7.12.5",
@@ -2569,7 +2566,6 @@
 			"version": "4.3.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
 			"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-			"dev": true,
 			"dependencies": {
 				"color-convert": "^2.0.1"
 			},
@@ -2584,7 +2580,6 @@
 			"version": "5.1.3",
 			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
 			"integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
-			"dev": true,
 			"dependencies": {
 				"deep-equal": "^2.0.5"
 			}
@@ -2593,7 +2588,6 @@
 			"version": "4.1.2",
 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 			"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-			"dev": true,
 			"dependencies": {
 				"ansi-styles": "^4.1.0",
 				"supports-color": "^7.1.0"
@@ -2609,7 +2603,6 @@
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 			"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-			"dev": true,
 			"dependencies": {
 				"color-name": "~1.1.4"
 			},
@@ -2620,14 +2613,12 @@
 		"node_modules/@testing-library/dom/node_modules/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==",
-			"dev": true
+			"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
 		},
 		"node_modules/@testing-library/dom/node_modules/has-flag": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -2636,7 +2627,6 @@
 			"version": "7.2.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-			"dev": true,
 			"dependencies": {
 				"has-flag": "^4.0.0"
 			},
@@ -2777,6 +2767,18 @@
 				"svelte": "^3 || ^4"
 			}
 		},
+		"node_modules/@testing-library/user-event": {
+			"version": "14.5.2",
+			"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz",
+			"integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==",
+			"engines": {
+				"node": ">=12",
+				"npm": ">=6"
+			},
+			"peerDependencies": {
+				"@testing-library/dom": ">=7.21.4"
+			}
+		},
 		"node_modules/@tootallnate/once": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -2843,8 +2845,7 @@
 		"node_modules/@types/aria-query": {
 			"version": "5.0.4",
 			"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
-			"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
-			"dev": true
+			"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="
 		},
 		"node_modules/@types/babel__core": {
 			"version": "7.20.5",
@@ -3649,7 +3650,6 @@
 			"version": "3.2.1",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
 			"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-			"dev": true,
 			"dependencies": {
 				"color-convert": "^1.9.0"
 			},
@@ -3733,7 +3733,6 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
 			"integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.5",
 				"is-array-buffer": "^3.0.4"
@@ -3838,7 +3837,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
 			"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
-			"dev": true,
 			"dependencies": {
 				"possible-typed-array-names": "^1.0.0"
 			},
@@ -4252,7 +4250,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
 			"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
-			"dev": true,
 			"dependencies": {
 				"es-define-property": "^1.0.0",
 				"es-errors": "^1.3.0",
@@ -4337,7 +4334,6 @@
 			"version": "2.4.2",
 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
 			"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-			"dev": true,
 			"dependencies": {
 				"ansi-styles": "^3.2.1",
 				"escape-string-regexp": "^1.0.5",
@@ -4530,7 +4526,6 @@
 			"version": "1.9.3",
 			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
 			"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-			"dev": true,
 			"dependencies": {
 				"color-name": "1.1.3"
 			}
@@ -4538,8 +4533,7 @@
 		"node_modules/color-name": {
 			"version": "1.1.3",
 			"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-			"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-			"dev": true
+			"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
 		},
 		"node_modules/color-support": {
 			"version": "1.1.3",
@@ -4899,7 +4893,6 @@
 			"version": "2.2.3",
 			"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
 			"integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
-			"dev": true,
 			"dependencies": {
 				"array-buffer-byte-length": "^1.0.0",
 				"call-bind": "^1.0.5",
@@ -4945,7 +4938,6 @@
 			"version": "1.1.4",
 			"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
 			"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
-			"dev": true,
 			"dependencies": {
 				"es-define-property": "^1.0.0",
 				"es-errors": "^1.3.0",
@@ -4962,7 +4954,6 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
 			"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
-			"dev": true,
 			"dependencies": {
 				"define-data-property": "^1.0.1",
 				"has-property-descriptors": "^1.0.0",
@@ -5084,8 +5075,7 @@
 		"node_modules/dom-accessibility-api": {
 			"version": "0.5.16",
 			"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
-			"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
-			"dev": true
+			"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="
 		},
 		"node_modules/domexception": {
 			"version": "4.0.0",
@@ -5179,7 +5169,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
 			"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
-			"dev": true,
 			"dependencies": {
 				"get-intrinsic": "^1.2.4"
 			},
@@ -5191,7 +5180,6 @@
 			"version": "1.3.0",
 			"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
 			"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			}
@@ -5200,7 +5188,6 @@
 			"version": "1.1.3",
 			"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
 			"integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"get-intrinsic": "^1.1.3",
@@ -5272,7 +5259,6 @@
 			"version": "1.0.5",
 			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
 			"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-			"dev": true,
 			"engines": {
 				"node": ">=0.8.0"
 			}
@@ -6001,7 +5987,6 @@
 			"version": "0.3.3",
 			"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
 			"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
-			"dev": true,
 			"dependencies": {
 				"is-callable": "^1.1.3"
 			}
@@ -6133,7 +6118,6 @@
 			"version": "1.2.3",
 			"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
 			"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-			"dev": true,
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -6192,7 +6176,6 @@
 			"version": "1.2.4",
 			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
 			"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
-			"dev": true,
 			"dependencies": {
 				"es-errors": "^1.3.0",
 				"function-bind": "^1.1.2",
@@ -6334,7 +6317,6 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
 			"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-			"dev": true,
 			"dependencies": {
 				"get-intrinsic": "^1.1.3"
 			},
@@ -6357,7 +6339,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
 			"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
-			"dev": true,
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -6366,7 +6347,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
 			"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-			"dev": true,
 			"engines": {
 				"node": ">=4"
 			}
@@ -6375,7 +6355,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
 			"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
-			"dev": true,
 			"dependencies": {
 				"es-define-property": "^1.0.0"
 			},
@@ -6387,7 +6366,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
 			"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6399,7 +6377,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
 			"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6411,7 +6388,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
 			"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
-			"dev": true,
 			"dependencies": {
 				"has-symbols": "^1.0.3"
 			},
@@ -6611,7 +6587,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
 			"integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
-			"dev": true,
 			"dependencies": {
 				"es-errors": "^1.3.0",
 				"hasown": "^2.0.0",
@@ -6625,7 +6600,6 @@
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
 			"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"has-tostringtag": "^1.0.0"
@@ -6641,7 +6615,6 @@
 			"version": "3.0.4",
 			"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
 			"integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"get-intrinsic": "^1.2.1"
@@ -6663,7 +6636,6 @@
 			"version": "1.0.4",
 			"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
 			"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
-			"dev": true,
 			"dependencies": {
 				"has-bigints": "^1.0.1"
 			},
@@ -6686,7 +6658,6 @@
 			"version": "1.1.2",
 			"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
 			"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"has-tostringtag": "^1.0.0"
@@ -6702,7 +6673,6 @@
 			"version": "1.2.7",
 			"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
 			"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6725,7 +6695,6 @@
 			"version": "1.0.5",
 			"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
 			"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-			"dev": true,
 			"dependencies": {
 				"has-tostringtag": "^1.0.0"
 			},
@@ -6800,7 +6769,6 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
 			"integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6820,7 +6788,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
 			"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
-			"dev": true,
 			"dependencies": {
 				"has-tostringtag": "^1.0.0"
 			},
@@ -6866,7 +6833,6 @@
 			"version": "1.1.4",
 			"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
 			"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"has-tostringtag": "^1.0.0"
@@ -6882,7 +6848,6 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
 			"integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6894,7 +6859,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
 			"integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7"
 			},
@@ -6921,7 +6885,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
 			"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
-			"dev": true,
 			"dependencies": {
 				"has-tostringtag": "^1.0.0"
 			},
@@ -6936,7 +6899,6 @@
 			"version": "1.0.4",
 			"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
 			"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
-			"dev": true,
 			"dependencies": {
 				"has-symbols": "^1.0.2"
 			},
@@ -6951,7 +6913,6 @@
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
 			"integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6963,7 +6924,6 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
 			"integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7",
 				"get-intrinsic": "^1.2.4"
@@ -6978,8 +6938,7 @@
 		"node_modules/isarray": {
 			"version": "2.0.5",
 			"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
-			"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
-			"dev": true
+			"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
 		},
 		"node_modules/isexe": {
 			"version": "2.0.0",
@@ -9341,8 +9300,7 @@
 		"node_modules/js-tokens": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-			"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-			"dev": true
+			"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
 		},
 		"node_modules/js-yaml": {
 			"version": "3.14.1",
@@ -9628,7 +9586,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
 			"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
-			"dev": true,
 			"bin": {
 				"lz-string": "bin/bin.js"
 			}
@@ -10045,7 +10002,6 @@
 			"version": "1.13.1",
 			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
 			"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
-			"dev": true,
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -10054,7 +10010,6 @@
 			"version": "1.1.6",
 			"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
 			"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7",
 				"define-properties": "^1.2.1"
@@ -10070,7 +10025,6 @@
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
 			"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			}
@@ -10079,7 +10033,6 @@
 			"version": "4.1.5",
 			"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
 			"integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.5",
 				"define-properties": "^1.2.1",
@@ -10396,7 +10349,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
 			"integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			}
@@ -10653,7 +10605,6 @@
 			"version": "27.5.1",
 			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
 			"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
-			"dev": true,
 			"dependencies": {
 				"ansi-regex": "^5.0.1",
 				"ansi-styles": "^5.0.0",
@@ -10667,7 +10618,6 @@
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
 			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-			"dev": true,
 			"engines": {
 				"node": ">=10"
 			},
@@ -10909,8 +10859,7 @@
 		"node_modules/react-is": {
 			"version": "17.0.2",
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
-			"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
-			"dev": true
+			"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 		},
 		"node_modules/read-cache": {
 			"version": "1.0.0",
@@ -10966,7 +10915,6 @@
 			"version": "1.5.2",
 			"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
 			"integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.6",
 				"define-properties": "^1.2.1",
@@ -11237,7 +11185,6 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
 			"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
-			"dev": true,
 			"dependencies": {
 				"define-data-property": "^1.1.2",
 				"es-errors": "^1.3.0",
@@ -11254,7 +11201,6 @@
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
 			"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
-			"dev": true,
 			"dependencies": {
 				"define-data-property": "^1.1.4",
 				"es-errors": "^1.3.0",
@@ -11288,7 +11234,6 @@
 			"version": "1.0.6",
 			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
 			"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7",
 				"es-errors": "^1.3.0",
@@ -11440,7 +11385,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
 			"integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
-			"dev": true,
 			"dependencies": {
 				"internal-slot": "^1.0.4"
 			},
@@ -11637,7 +11581,6 @@
 			"version": "5.5.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
 			"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-			"dev": true,
 			"dependencies": {
 				"has-flag": "^3.0.0"
 			},
@@ -12643,7 +12586,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
 			"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
-			"dev": true,
 			"dependencies": {
 				"is-bigint": "^1.0.1",
 				"is-boolean-object": "^1.1.0",
@@ -12659,7 +12601,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
 			"integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
-			"dev": true,
 			"dependencies": {
 				"is-map": "^2.0.3",
 				"is-set": "^2.0.3",
@@ -12677,7 +12618,6 @@
 			"version": "1.1.15",
 			"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
 			"integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
-			"dev": true,
 			"dependencies": {
 				"available-typed-arrays": "^1.0.7",
 				"call-bind": "^1.0.7",
diff --git a/frontend/package.json b/frontend/package.json
index 26b3b96..43f8feb 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -44,6 +44,7 @@
 	"dependencies": {
 		"@popperjs/core": "^2.11.8",
 		"@sveltejs/adapter-vercel": "^5.1.0",
+		"@testing-library/user-event": "^14.5.2",
 		"classnames": "^2.5.1",
 		"date-picker-svelte": "^2.11.0",
 		"dotenv": "^16.4.5",
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
new file mode 100644
index 0000000..ab53e24
--- /dev/null
+++ b/frontend/test/components/UnitOverview.test.js
@@ -0,0 +1,114 @@
+/* eslint-disable no-undef */
+import { render, screen } from '@testing-library/svelte';
+import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
+import userEvent from '@testing-library/user-event';
+
+describe('UnitOverview', () => {
+    test('renders the course title and id correctly', () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        expect(screen.getByText('CS101 - Introduction to Computer Science')).toBeInTheDocument();
+    });
+
+    test('toggles unit creation modal visibility', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const createButton = screen.getByText('+ Create new unit');
+        await userEvent.click(createButton);
+        expect(screen.getByText('Create unit')).toBeInTheDocument();
+    });
+
+    test('toggles invitation modal visibility', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const inviteButton = screen.getByText('+ Invite');
+        await userEvent.click(inviteButton);
+        expect(screen.getByText('Invite Users')).toBeInTheDocument();
+    });
+
+    test('create unit form submission', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const createButton = screen.getByText('+ Create new unit');
+        await userEvent.click(createButton);
+
+        await userEvent.type(screen.getByPlaceholderText('title'), 'New Unit');
+        await userEvent.click(screen.getByText('Submit'));
+
+        // Simulating successful unit creation response here
+        expect(screen.queryByText('Unit successfully created!')).toBeInTheDocument();
+    });
+
+    test('inviting users through the form', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const inviteButton = screen.getByText('+ Invite');
+        await userEvent.click(inviteButton);
+
+        await userEvent.type(screen.getByPlaceholderText('example'), 'user1 user2');
+        await userEvent.click(screen.getByText('Submit'));
+
+        // Simulating successful invitation sending here
+        expect(screen.queryByText('Invitations has been sent!')).toBeInTheDocument();
+    });
+
+    test('handling error in form submissions', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const createButton = screen.getByText('+ Create new unit');
+        await userEvent.click(createButton);
+
+        await userEvent.type(screen.getByPlaceholderText('title'), ''); // Empty title to trigger validation error
+        await userEvent.click(screen.getByText('Submit'));
+
+        expect(screen.getByText('Could not create unit')).toBeInTheDocument();
+    });
+});
diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 1e5d5e2..3e162d1 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -1,14 +1,21 @@
 import { defineConfig } from 'vitest/config';
 import { svelte } from '@sveltejs/vite-plugin-svelte';
+import { resolve } from 'path';
 
 export default defineConfig({
-	plugins: [svelte()],
-	test: {
-		globals: true,
-		environment: 'jsdom',
-		setupFiles: ['./test/setupTests.js'],
-		transformMode: {
-			web: [/.[tj]sx?$/, /.svelte$/]
-		}
-	}
-});
+    plugins: [svelte()],
+    resolve: {
+        alias: {
+            $app: resolve(__dirname, './src/lib'),
+			// $env: resolve(__dirname, './src/env')
+        }
+    },
+    test: {
+        globals: true,
+        environment: 'jsdom',
+        setupFiles: ['./test/setupTests.js'],
+        transformMode: {
+            web: [/.[tj]sx?$/, /.svelte$/]
+        }
+    }
+});
\ No newline at end of file
-- 
GitLab


From 7fe07503f073d57095df7ca7a78942ab5941a257 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:14:25 +0200
Subject: [PATCH 03/59] todo

---
 frontend/vitest.config.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 3e162d1..160eff8 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -6,8 +6,10 @@ export default defineConfig({
     plugins: [svelte()],
     resolve: {
         alias: {
+			$lib: resolve(__dirname, './src/lib'),
+			// TODO FIX IMPORT UNDER
             $app: resolve(__dirname, './src/lib'),
-			// $env: resolve(__dirname, './src/env')
+			$env: resolve(__dirname, './src/env'),
         }
     },
     test: {
-- 
GitLab


From 2dcd28f0047bb9d2f587432db0d6beb15e35dac5 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:19:32 +0200
Subject: [PATCH 04/59] fix app

---
 frontend/vitest.config.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 160eff8..0f2df0e 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -8,7 +8,7 @@ export default defineConfig({
         alias: {
 			$lib: resolve(__dirname, './src/lib'),
 			// TODO FIX IMPORT UNDER
-            $app: resolve(__dirname, './src/lib'),
+            $app: resolve(__dirname, './node_modules/@sveltejs/kit/src/runtime/app'),
 			$env: resolve(__dirname, './src/env'),
         }
     },
-- 
GitLab


From 4c5d15a736cd7d6c559e1cb901cac074ce3c238f Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:49:15 +0200
Subject: [PATCH 05/59] mocking

---
 frontend/__mocks__/app.js | 0
 frontend/__mocks__/env.js | 0
 frontend/vitest.config.js | 5 ++---
 3 files changed, 2 insertions(+), 3 deletions(-)
 create mode 100644 frontend/__mocks__/app.js
 create mode 100644 frontend/__mocks__/env.js

diff --git a/frontend/__mocks__/app.js b/frontend/__mocks__/app.js
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/__mocks__/env.js b/frontend/__mocks__/env.js
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 0f2df0e..8406482 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -7,9 +7,8 @@ export default defineConfig({
     resolve: {
         alias: {
 			$lib: resolve(__dirname, './src/lib'),
-			// TODO FIX IMPORT UNDER
-            $app: resolve(__dirname, './node_modules/@sveltejs/kit/src/runtime/app'),
-			$env: resolve(__dirname, './src/env'),
+            $app: resolve(__dirname, './__mocks__/app'),
+            $env: resolve(__dirname, './__mocks__/env'),
         }
     },
     test: {
-- 
GitLab


From 2238f2940a4de41f92188f3e505dd6efd9708e20 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:57:44 +0200
Subject: [PATCH 06/59] mocking

---
 .gitignore                              |  4 ++--
 frontend/.gitignore                     |  5 +----
 frontend/__mocks__/app.js               |  0
 frontend/__mocks__/app/navigation.js    | 17 +++++++++++++++++
 frontend/__mocks__/env.js               |  0
 frontend/__mocks__/env/static/public.js |  6 ++++++
 6 files changed, 26 insertions(+), 6 deletions(-)
 delete mode 100644 frontend/__mocks__/app.js
 create mode 100644 frontend/__mocks__/app/navigation.js
 delete mode 100644 frontend/__mocks__/env.js
 create mode 100644 frontend/__mocks__/env/static/public.js

diff --git a/.gitignore b/.gitignore
index 23c4d93..c7feb33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,9 +108,9 @@ celerybeat.pid
 # Environments
 .env
 .venv
-env/
+
 venv/
-ENV/
+
 env.bak/
 venv.bak/
 .nvmrc
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 61bf367..dbd0e02 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -3,10 +3,7 @@ node_modules
 /build
 /.svelte-kit
 /package
-.env
-.env.*
-!.env.example
-!.env.template
+
 .vercel
 .output
 vite.config.js.timestamp-*
diff --git a/frontend/__mocks__/app.js b/frontend/__mocks__/app.js
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/__mocks__/app/navigation.js b/frontend/__mocks__/app/navigation.js
new file mode 100644
index 0000000..9830089
--- /dev/null
+++ b/frontend/__mocks__/app/navigation.js
@@ -0,0 +1,17 @@
+import { jest } from '@jest/globals';
+
+const goto = jest.fn();
+const invalidate = jest.fn();
+const invalidateAll = jest.fn();
+
+jest.mock('$app/navigation', () => ({
+  goto,
+  invalidate,
+  invalidateAll,
+}));
+
+module.exports = {
+  goto,
+  invalidate,
+  invalidateAll,
+};
\ No newline at end of file
diff --git a/frontend/__mocks__/env.js b/frontend/__mocks__/env.js
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/__mocks__/env/static/public.js b/frontend/__mocks__/env/static/public.js
new file mode 100644
index 0000000..bf9c454
--- /dev/null
+++ b/frontend/__mocks__/env/static/public.js
@@ -0,0 +1,6 @@
+
+const PUBLIC_API_URL = 'http://localhost:3000/api';
+
+module.exports = {
+    PUBLIC_API_URL
+  };
\ No newline at end of file
-- 
GitLab


From 2d0012b3f3e81965b2ee41bd6a15bf8f0e236529 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:02:35 +0200
Subject: [PATCH 07/59] mocking

---
 frontend/__mocks__/app/navigation.js          |  14 +--
 frontend/test/components/UnitOverview.test.js | 116 +-----------------
 2 files changed, 10 insertions(+), 120 deletions(-)

diff --git a/frontend/__mocks__/app/navigation.js b/frontend/__mocks__/app/navigation.js
index 9830089..778cb06 100644
--- a/frontend/__mocks__/app/navigation.js
+++ b/frontend/__mocks__/app/navigation.js
@@ -1,14 +1,8 @@
-import { jest } from '@jest/globals';
+import { vi } from "vitest";
 
-const goto = jest.fn();
-const invalidate = jest.fn();
-const invalidateAll = jest.fn();
-
-jest.mock('$app/navigation', () => ({
-  goto,
-  invalidate,
-  invalidateAll,
-}));
+const goto = vi.fn();
+const invalidate = vi.fn();
+const invalidateAll = vi.fn();
 
 module.exports = {
   goto,
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index ab53e24..a0011c3 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,114 +1,10 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
-import userEvent from '@testing-library/user-event';
 
-describe('UnitOverview', () => {
-    test('renders the course title and id correctly', () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        expect(screen.getByText('CS101 - Introduction to Computer Science')).toBeInTheDocument();
-    });
-
-    test('toggles unit creation modal visibility', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const createButton = screen.getByText('+ Create new unit');
-        await userEvent.click(createButton);
-        expect(screen.getByText('Create unit')).toBeInTheDocument();
-    });
-
-    test('toggles invitation modal visibility', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const inviteButton = screen.getByText('+ Invite');
-        await userEvent.click(inviteButton);
-        expect(screen.getByText('Invite Users')).toBeInTheDocument();
-    });
-
-    test('create unit form submission', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const createButton = screen.getByText('+ Create new unit');
-        await userEvent.click(createButton);
-
-        await userEvent.type(screen.getByPlaceholderText('title'), 'New Unit');
-        await userEvent.click(screen.getByText('Submit'));
-
-        // Simulating successful unit creation response here
-        expect(screen.queryByText('Unit successfully created!')).toBeInTheDocument();
-    });
-
-    test('inviting users through the form', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const inviteButton = screen.getByText('+ Invite');
-        await userEvent.click(inviteButton);
-
-        await userEvent.type(screen.getByPlaceholderText('example'), 'user1 user2');
-        await userEvent.click(screen.getByText('Submit'));
-
-        // Simulating successful invitation sending here
-        expect(screen.queryByText('Invitations has been sent!')).toBeInTheDocument();
-    });
-
-    test('handling error in form submissions', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const createButton = screen.getByText('+ Create new unit');
-        await userEvent.click(createButton);
-
-        await userEvent.type(screen.getByPlaceholderText('title'), ''); // Empty title to trigger validation error
-        await userEvent.click(screen.getByText('Submit'));
-
-        expect(screen.getByText('Could not create unit')).toBeInTheDocument();
-    });
-});
+test('renders UnitOverview component', () => {
+    render(UnitOverview);
+    const linkElement = screen.getByText(/Unit Overview/i);
+    expect(linkElement).toBeInTheDocument();
+    }
+);
\ No newline at end of file
-- 
GitLab


From 3c053342132f454ddc7913ddfb88ec740a68d6ce Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:12:33 +0200
Subject: [PATCH 08/59] Mocking

---
 frontend/__mocks__/Data.js                    | 156 ++++++++++++++++++
 frontend/test/components/UnitOverview.test.js |   7 +-
 2 files changed, 159 insertions(+), 4 deletions(-)
 create mode 100644 frontend/__mocks__/Data.js

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
new file mode 100644
index 0000000..8fc7f2a
--- /dev/null
+++ b/frontend/__mocks__/Data.js
@@ -0,0 +1,156 @@
+const mockData = [
+    {
+        user: {
+            uid: 'test',
+            email: 'test@ntnu.no',
+            enrollments: [
+                {
+                    course_id: 'TDT4000',
+                    course_semester: 'spring2024',
+                    role: 'lecturer',
+                    uid: 'test',
+                    missingUnits: []
+                },
+                {
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023',
+                    role: 'student',
+                    uid: 'test',
+                    missingUnits: [
+                        {
+                            id: 1,
+                            date: '2022-08-23'
+                        },
+                        {
+                            id: 2,
+                            date: '2022-08-30'
+                        }
+                    ]
+                }
+            ],
+            reflections: [],
+            admin: true
+        },
+        course: {
+            id: 'TDT4100',
+            semester: 'fall2023',
+            name: 'Informasjonsteknologi grunnkurs',
+            responsible: '',
+            website: '',
+            questions: [
+                {
+                    id: 1,
+                    question: 'Teaching',
+                    comment: 'What was your best learning success in this unit? Why?'
+                },
+                {
+                    id: 2,
+                    question: 'Difficult',
+                    comment: 'What was your least understood concept in this unit? Why?'
+                }
+            ],
+            users: [
+                {
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023',
+                    role: 'student'
+                }
+            ],
+            reports: [
+                {
+                    report_content: [],
+                    number_of_answers: 0,
+                    unit_id: 1,
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023'
+                },
+                {
+                    report_content: [],
+                    number_of_answers: 0,
+                    unit_id: 2,
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023'
+                },
+                {
+                    report_content: [],
+                    number_of_answers: 0,
+                    unit_id: 3,
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023'
+                }
+            ]
+        },
+        course_name: 'TDT4100',
+        role: 'student',
+        units: [
+            {
+                hidden: false,
+                title: 'State Machines',
+                date_available: '2022-08-23',
+                course_id: 'TDT4100',
+                course_semester: 'fall2023',
+                id: 1,
+                reflections: [],
+                unit_number: 1
+            },
+            {
+                hidden: false,
+                title: 'HTTP og JSON',
+                date_available: '2022-08-30',
+                course_id: 'TDT4100',
+                course_semester: 'fall2023',
+                id: 2,
+                reflections: [],
+                unit_number: 2
+            },
+            {
+                hidden: false,
+                title: 'MQTT Chat',
+                date_available: '2024-09-07',
+                course_id: 'TDT4100',
+                course_semester: 'fall2023',
+                id: 3,
+                reflections: [],
+                unit_number: 3
+            }
+        ]
+    }
+];
+
+const mockUnits = [
+    {
+        hidden: false,
+        title: 'State Machines',
+        date_available: '2022-08-23',
+        course_id: 'TDT4100',
+        course_semester: 'fall2023',
+        id: 1,
+        reflections: [],
+        unit_number: 1
+    },
+    {
+        hidden: false,
+        title: 'HTTP og JSON',
+        date_available: '2022-08-30',
+        course_id: 'TDT4100',
+        course_semester: 'fall2023',
+        id: 2,
+        reflections: [],
+        unit_number: 2
+    },
+    {
+        hidden: false,
+        title: 'MQTT Chat',
+        date_available: '2024-09-07',
+        course_id: 'TDT4100',
+        course_semester: 'fall2023',
+        id: 3,
+        reflections: [],
+        unit_number: 3
+    }
+];
+
+module.exports = {
+    mockData,
+    mockUnits
+};
\ No newline at end of file
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index a0011c3..ccfe68a 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,10 +1,9 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
+import { render } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
+import { mockData, mockUnits } from '../../__mocks__/Data';
 
 test('renders UnitOverview component', () => {
-    render(UnitOverview);
-    const linkElement = screen.getByText(/Unit Overview/i);
-    expect(linkElement).toBeInTheDocument();
+    render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
     }
 );
\ No newline at end of file
-- 
GitLab


From 4304dda940a95792eeffdfea7f285ca83a1a42d9 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:13:52 +0200
Subject: [PATCH 09/59] Lint

---
 frontend/__mocks__/Data.js                    | 298 +++++++++---------
 frontend/__mocks__/app/navigation.js          |  10 +-
 frontend/__mocks__/env/static/public.js       |   5 +-
 frontend/test/components/UnitOverview.test.js |   5 +-
 frontend/vitest.config.js                     |  32 +-
 5 files changed, 174 insertions(+), 176 deletions(-)

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 8fc7f2a..3a1506a 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -1,156 +1,156 @@
 const mockData = [
-    {
-        user: {
-            uid: 'test',
-            email: 'test@ntnu.no',
-            enrollments: [
-                {
-                    course_id: 'TDT4000',
-                    course_semester: 'spring2024',
-                    role: 'lecturer',
-                    uid: 'test',
-                    missingUnits: []
-                },
-                {
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023',
-                    role: 'student',
-                    uid: 'test',
-                    missingUnits: [
-                        {
-                            id: 1,
-                            date: '2022-08-23'
-                        },
-                        {
-                            id: 2,
-                            date: '2022-08-30'
-                        }
-                    ]
-                }
-            ],
-            reflections: [],
-            admin: true
-        },
-        course: {
-            id: 'TDT4100',
-            semester: 'fall2023',
-            name: 'Informasjonsteknologi grunnkurs',
-            responsible: '',
-            website: '',
-            questions: [
-                {
-                    id: 1,
-                    question: 'Teaching',
-                    comment: 'What was your best learning success in this unit? Why?'
-                },
-                {
-                    id: 2,
-                    question: 'Difficult',
-                    comment: 'What was your least understood concept in this unit? Why?'
-                }
-            ],
-            users: [
-                {
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023',
-                    role: 'student'
-                }
-            ],
-            reports: [
-                {
-                    report_content: [],
-                    number_of_answers: 0,
-                    unit_id: 1,
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023'
-                },
-                {
-                    report_content: [],
-                    number_of_answers: 0,
-                    unit_id: 2,
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023'
-                },
-                {
-                    report_content: [],
-                    number_of_answers: 0,
-                    unit_id: 3,
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023'
-                }
-            ]
-        },
-        course_name: 'TDT4100',
-        role: 'student',
-        units: [
-            {
-                hidden: false,
-                title: 'State Machines',
-                date_available: '2022-08-23',
-                course_id: 'TDT4100',
-                course_semester: 'fall2023',
-                id: 1,
-                reflections: [],
-                unit_number: 1
-            },
-            {
-                hidden: false,
-                title: 'HTTP og JSON',
-                date_available: '2022-08-30',
-                course_id: 'TDT4100',
-                course_semester: 'fall2023',
-                id: 2,
-                reflections: [],
-                unit_number: 2
-            },
-            {
-                hidden: false,
-                title: 'MQTT Chat',
-                date_available: '2024-09-07',
-                course_id: 'TDT4100',
-                course_semester: 'fall2023',
-                id: 3,
-                reflections: [],
-                unit_number: 3
-            }
-        ]
-    }
+	{
+		user: {
+			uid: 'test',
+			email: 'test@ntnu.no',
+			enrollments: [
+				{
+					course_id: 'TDT4000',
+					course_semester: 'spring2024',
+					role: 'lecturer',
+					uid: 'test',
+					missingUnits: []
+				},
+				{
+					course_id: 'TDT4100',
+					course_semester: 'fall2023',
+					role: 'student',
+					uid: 'test',
+					missingUnits: [
+						{
+							id: 1,
+							date: '2022-08-23'
+						},
+						{
+							id: 2,
+							date: '2022-08-30'
+						}
+					]
+				}
+			],
+			reflections: [],
+			admin: true
+		},
+		course: {
+			id: 'TDT4100',
+			semester: 'fall2023',
+			name: 'Informasjonsteknologi grunnkurs',
+			responsible: '',
+			website: '',
+			questions: [
+				{
+					id: 1,
+					question: 'Teaching',
+					comment: 'What was your best learning success in this unit? Why?'
+				},
+				{
+					id: 2,
+					question: 'Difficult',
+					comment: 'What was your least understood concept in this unit? Why?'
+				}
+			],
+			users: [
+				{
+					course_id: 'TDT4100',
+					course_semester: 'fall2023',
+					role: 'student'
+				}
+			],
+			reports: [
+				{
+					report_content: [],
+					number_of_answers: 0,
+					unit_id: 1,
+					course_id: 'TDT4100',
+					course_semester: 'fall2023'
+				},
+				{
+					report_content: [],
+					number_of_answers: 0,
+					unit_id: 2,
+					course_id: 'TDT4100',
+					course_semester: 'fall2023'
+				},
+				{
+					report_content: [],
+					number_of_answers: 0,
+					unit_id: 3,
+					course_id: 'TDT4100',
+					course_semester: 'fall2023'
+				}
+			]
+		},
+		course_name: 'TDT4100',
+		role: 'student',
+		units: [
+			{
+				hidden: false,
+				title: 'State Machines',
+				date_available: '2022-08-23',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 1,
+				reflections: [],
+				unit_number: 1
+			},
+			{
+				hidden: false,
+				title: 'HTTP og JSON',
+				date_available: '2022-08-30',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 2,
+				reflections: [],
+				unit_number: 2
+			},
+			{
+				hidden: false,
+				title: 'MQTT Chat',
+				date_available: '2024-09-07',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 3,
+				reflections: [],
+				unit_number: 3
+			}
+		]
+	}
 ];
 
 const mockUnits = [
-    {
-        hidden: false,
-        title: 'State Machines',
-        date_available: '2022-08-23',
-        course_id: 'TDT4100',
-        course_semester: 'fall2023',
-        id: 1,
-        reflections: [],
-        unit_number: 1
-    },
-    {
-        hidden: false,
-        title: 'HTTP og JSON',
-        date_available: '2022-08-30',
-        course_id: 'TDT4100',
-        course_semester: 'fall2023',
-        id: 2,
-        reflections: [],
-        unit_number: 2
-    },
-    {
-        hidden: false,
-        title: 'MQTT Chat',
-        date_available: '2024-09-07',
-        course_id: 'TDT4100',
-        course_semester: 'fall2023',
-        id: 3,
-        reflections: [],
-        unit_number: 3
-    }
+	{
+		hidden: false,
+		title: 'State Machines',
+		date_available: '2022-08-23',
+		course_id: 'TDT4100',
+		course_semester: 'fall2023',
+		id: 1,
+		reflections: [],
+		unit_number: 1
+	},
+	{
+		hidden: false,
+		title: 'HTTP og JSON',
+		date_available: '2022-08-30',
+		course_id: 'TDT4100',
+		course_semester: 'fall2023',
+		id: 2,
+		reflections: [],
+		unit_number: 2
+	},
+	{
+		hidden: false,
+		title: 'MQTT Chat',
+		date_available: '2024-09-07',
+		course_id: 'TDT4100',
+		course_semester: 'fall2023',
+		id: 3,
+		reflections: [],
+		unit_number: 3
+	}
 ];
 
 module.exports = {
-    mockData,
-    mockUnits
-};
\ No newline at end of file
+	mockData,
+	mockUnits
+};
diff --git a/frontend/__mocks__/app/navigation.js b/frontend/__mocks__/app/navigation.js
index 778cb06..0019ff5 100644
--- a/frontend/__mocks__/app/navigation.js
+++ b/frontend/__mocks__/app/navigation.js
@@ -1,11 +1,11 @@
-import { vi } from "vitest";
+import { vi } from 'vitest';
 
 const goto = vi.fn();
 const invalidate = vi.fn();
 const invalidateAll = vi.fn();
 
 module.exports = {
-  goto,
-  invalidate,
-  invalidateAll,
-};
\ No newline at end of file
+	goto,
+	invalidate,
+	invalidateAll
+};
diff --git a/frontend/__mocks__/env/static/public.js b/frontend/__mocks__/env/static/public.js
index bf9c454..c69636e 100644
--- a/frontend/__mocks__/env/static/public.js
+++ b/frontend/__mocks__/env/static/public.js
@@ -1,6 +1,5 @@
-
 const PUBLIC_API_URL = 'http://localhost:3000/api';
 
 module.exports = {
-    PUBLIC_API_URL
-  };
\ No newline at end of file
+	PUBLIC_API_URL
+};
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index ccfe68a..cb75431 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -4,6 +4,5 @@ import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
 import { mockData, mockUnits } from '../../__mocks__/Data';
 
 test('renders UnitOverview component', () => {
-    render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
-    }
-);
\ No newline at end of file
+	render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
+});
diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 8406482..b8c4f59 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -3,20 +3,20 @@ import { svelte } from '@sveltejs/vite-plugin-svelte';
 import { resolve } from 'path';
 
 export default defineConfig({
-    plugins: [svelte()],
-    resolve: {
-        alias: {
+	plugins: [svelte()],
+	resolve: {
+		alias: {
 			$lib: resolve(__dirname, './src/lib'),
-            $app: resolve(__dirname, './__mocks__/app'),
-            $env: resolve(__dirname, './__mocks__/env'),
-        }
-    },
-    test: {
-        globals: true,
-        environment: 'jsdom',
-        setupFiles: ['./test/setupTests.js'],
-        transformMode: {
-            web: [/.[tj]sx?$/, /.svelte$/]
-        }
-    }
-});
\ No newline at end of file
+			$app: resolve(__dirname, './__mocks__/app'),
+			$env: resolve(__dirname, './__mocks__/env')
+		}
+	},
+	test: {
+		globals: true,
+		environment: 'jsdom',
+		setupFiles: ['./test/setupTests.js'],
+		transformMode: {
+			web: [/.[tj]sx?$/, /.svelte$/]
+		}
+	}
+});
-- 
GitLab


From 87bbc368f71749009cab02a907a51450f97892e1 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:14:27 +0200
Subject: [PATCH 10/59] pipeline

---
 .gitlab-ci.yml | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 61cd8b0..5e29931 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -93,6 +93,17 @@ test backend:
   only:
     - merge_requests
 
+test frontend:
+  stage: test
+  image: node:20
+  script:
+    - echo "Testing the frontend"
+    - cd frontend
+    - npm i --cache .npm --prefer-offline
+    - npm run test
+  only:
+    - merge_requests
+
 deploy-frontend:
   stage: deploy
   image: node:19
-- 
GitLab


From e552e62ca193876955a4aea448e2c1cf2a5dad83 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:11:14 +0200
Subject: [PATCH 11/59] testing

---
 frontend/package-lock.json                    | 100 +++------------
 frontend/package.json                         |   1 +
 .../test/{ => components}/Breadcrumb.test.js  |   2 +-
 frontend/test/components/UnitOverview.test.js | 114 ++++++++++++++++++
 frontend/vitest.config.js                     |  27 +++--
 5 files changed, 153 insertions(+), 91 deletions(-)
 rename frontend/test/{ => components}/Breadcrumb.test.js (92%)
 create mode 100644 frontend/test/components/UnitOverview.test.js

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 58e611e..9182ae8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
 			"dependencies": {
 				"@popperjs/core": "^2.11.8",
 				"@sveltejs/adapter-vercel": "^5.1.0",
+				"@testing-library/user-event": "^14.5.2",
 				"classnames": "^2.5.1",
 				"date-picker-svelte": "^2.11.0",
 				"dotenv": "^16.4.5",
@@ -90,7 +91,6 @@
 			"version": "7.23.5",
 			"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
 			"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
-			"dev": true,
 			"dependencies": {
 				"@babel/highlight": "^7.23.4",
 				"chalk": "^2.4.2"
@@ -304,7 +304,6 @@
 			"version": "7.22.20",
 			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
 			"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
-			"dev": true,
 			"engines": {
 				"node": ">=6.9.0"
 			}
@@ -340,7 +339,6 @@
 			"version": "7.23.4",
 			"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
 			"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
-			"dev": true,
 			"dependencies": {
 				"@babel/helper-validator-identifier": "^7.22.20",
 				"chalk": "^2.4.2",
@@ -2550,7 +2548,6 @@
 			"version": "9.3.4",
 			"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz",
 			"integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==",
-			"dev": true,
 			"dependencies": {
 				"@babel/code-frame": "^7.10.4",
 				"@babel/runtime": "^7.12.5",
@@ -2569,7 +2566,6 @@
 			"version": "4.3.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
 			"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-			"dev": true,
 			"dependencies": {
 				"color-convert": "^2.0.1"
 			},
@@ -2584,7 +2580,6 @@
 			"version": "5.1.3",
 			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
 			"integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
-			"dev": true,
 			"dependencies": {
 				"deep-equal": "^2.0.5"
 			}
@@ -2593,7 +2588,6 @@
 			"version": "4.1.2",
 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 			"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-			"dev": true,
 			"dependencies": {
 				"ansi-styles": "^4.1.0",
 				"supports-color": "^7.1.0"
@@ -2609,7 +2603,6 @@
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 			"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-			"dev": true,
 			"dependencies": {
 				"color-name": "~1.1.4"
 			},
@@ -2620,14 +2613,12 @@
 		"node_modules/@testing-library/dom/node_modules/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==",
-			"dev": true
+			"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
 		},
 		"node_modules/@testing-library/dom/node_modules/has-flag": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -2636,7 +2627,6 @@
 			"version": "7.2.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-			"dev": true,
 			"dependencies": {
 				"has-flag": "^4.0.0"
 			},
@@ -2777,6 +2767,18 @@
 				"svelte": "^3 || ^4"
 			}
 		},
+		"node_modules/@testing-library/user-event": {
+			"version": "14.5.2",
+			"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz",
+			"integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==",
+			"engines": {
+				"node": ">=12",
+				"npm": ">=6"
+			},
+			"peerDependencies": {
+				"@testing-library/dom": ">=7.21.4"
+			}
+		},
 		"node_modules/@tootallnate/once": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -2843,8 +2845,7 @@
 		"node_modules/@types/aria-query": {
 			"version": "5.0.4",
 			"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
-			"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
-			"dev": true
+			"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="
 		},
 		"node_modules/@types/babel__core": {
 			"version": "7.20.5",
@@ -3649,7 +3650,6 @@
 			"version": "3.2.1",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
 			"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-			"dev": true,
 			"dependencies": {
 				"color-convert": "^1.9.0"
 			},
@@ -3733,7 +3733,6 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
 			"integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.5",
 				"is-array-buffer": "^3.0.4"
@@ -3838,7 +3837,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
 			"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
-			"dev": true,
 			"dependencies": {
 				"possible-typed-array-names": "^1.0.0"
 			},
@@ -4252,7 +4250,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
 			"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
-			"dev": true,
 			"dependencies": {
 				"es-define-property": "^1.0.0",
 				"es-errors": "^1.3.0",
@@ -4337,7 +4334,6 @@
 			"version": "2.4.2",
 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
 			"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-			"dev": true,
 			"dependencies": {
 				"ansi-styles": "^3.2.1",
 				"escape-string-regexp": "^1.0.5",
@@ -4530,7 +4526,6 @@
 			"version": "1.9.3",
 			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
 			"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-			"dev": true,
 			"dependencies": {
 				"color-name": "1.1.3"
 			}
@@ -4538,8 +4533,7 @@
 		"node_modules/color-name": {
 			"version": "1.1.3",
 			"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-			"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-			"dev": true
+			"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
 		},
 		"node_modules/color-support": {
 			"version": "1.1.3",
@@ -4899,7 +4893,6 @@
 			"version": "2.2.3",
 			"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
 			"integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
-			"dev": true,
 			"dependencies": {
 				"array-buffer-byte-length": "^1.0.0",
 				"call-bind": "^1.0.5",
@@ -4945,7 +4938,6 @@
 			"version": "1.1.4",
 			"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
 			"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
-			"dev": true,
 			"dependencies": {
 				"es-define-property": "^1.0.0",
 				"es-errors": "^1.3.0",
@@ -4962,7 +4954,6 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
 			"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
-			"dev": true,
 			"dependencies": {
 				"define-data-property": "^1.0.1",
 				"has-property-descriptors": "^1.0.0",
@@ -5084,8 +5075,7 @@
 		"node_modules/dom-accessibility-api": {
 			"version": "0.5.16",
 			"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
-			"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
-			"dev": true
+			"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="
 		},
 		"node_modules/domexception": {
 			"version": "4.0.0",
@@ -5179,7 +5169,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
 			"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
-			"dev": true,
 			"dependencies": {
 				"get-intrinsic": "^1.2.4"
 			},
@@ -5191,7 +5180,6 @@
 			"version": "1.3.0",
 			"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
 			"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			}
@@ -5200,7 +5188,6 @@
 			"version": "1.1.3",
 			"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
 			"integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"get-intrinsic": "^1.1.3",
@@ -5272,7 +5259,6 @@
 			"version": "1.0.5",
 			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
 			"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-			"dev": true,
 			"engines": {
 				"node": ">=0.8.0"
 			}
@@ -6001,7 +5987,6 @@
 			"version": "0.3.3",
 			"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
 			"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
-			"dev": true,
 			"dependencies": {
 				"is-callable": "^1.1.3"
 			}
@@ -6133,7 +6118,6 @@
 			"version": "1.2.3",
 			"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
 			"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-			"dev": true,
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -6192,7 +6176,6 @@
 			"version": "1.2.4",
 			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
 			"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
-			"dev": true,
 			"dependencies": {
 				"es-errors": "^1.3.0",
 				"function-bind": "^1.1.2",
@@ -6334,7 +6317,6 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
 			"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-			"dev": true,
 			"dependencies": {
 				"get-intrinsic": "^1.1.3"
 			},
@@ -6357,7 +6339,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
 			"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
-			"dev": true,
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -6366,7 +6347,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
 			"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
-			"dev": true,
 			"engines": {
 				"node": ">=4"
 			}
@@ -6375,7 +6355,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
 			"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
-			"dev": true,
 			"dependencies": {
 				"es-define-property": "^1.0.0"
 			},
@@ -6387,7 +6366,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
 			"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6399,7 +6377,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
 			"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6411,7 +6388,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
 			"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
-			"dev": true,
 			"dependencies": {
 				"has-symbols": "^1.0.3"
 			},
@@ -6611,7 +6587,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
 			"integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
-			"dev": true,
 			"dependencies": {
 				"es-errors": "^1.3.0",
 				"hasown": "^2.0.0",
@@ -6625,7 +6600,6 @@
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
 			"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"has-tostringtag": "^1.0.0"
@@ -6641,7 +6615,6 @@
 			"version": "3.0.4",
 			"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
 			"integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"get-intrinsic": "^1.2.1"
@@ -6663,7 +6636,6 @@
 			"version": "1.0.4",
 			"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
 			"integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
-			"dev": true,
 			"dependencies": {
 				"has-bigints": "^1.0.1"
 			},
@@ -6686,7 +6658,6 @@
 			"version": "1.1.2",
 			"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
 			"integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"has-tostringtag": "^1.0.0"
@@ -6702,7 +6673,6 @@
 			"version": "1.2.7",
 			"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
 			"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6725,7 +6695,6 @@
 			"version": "1.0.5",
 			"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
 			"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-			"dev": true,
 			"dependencies": {
 				"has-tostringtag": "^1.0.0"
 			},
@@ -6800,7 +6769,6 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
 			"integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6820,7 +6788,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
 			"integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
-			"dev": true,
 			"dependencies": {
 				"has-tostringtag": "^1.0.0"
 			},
@@ -6866,7 +6833,6 @@
 			"version": "1.1.4",
 			"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
 			"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.2",
 				"has-tostringtag": "^1.0.0"
@@ -6882,7 +6848,6 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
 			"integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6894,7 +6859,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
 			"integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7"
 			},
@@ -6921,7 +6885,6 @@
 			"version": "1.0.7",
 			"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
 			"integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
-			"dev": true,
 			"dependencies": {
 				"has-tostringtag": "^1.0.0"
 			},
@@ -6936,7 +6899,6 @@
 			"version": "1.0.4",
 			"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
 			"integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
-			"dev": true,
 			"dependencies": {
 				"has-symbols": "^1.0.2"
 			},
@@ -6951,7 +6913,6 @@
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
 			"integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			},
@@ -6963,7 +6924,6 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz",
 			"integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7",
 				"get-intrinsic": "^1.2.4"
@@ -6978,8 +6938,7 @@
 		"node_modules/isarray": {
 			"version": "2.0.5",
 			"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
-			"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
-			"dev": true
+			"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
 		},
 		"node_modules/isexe": {
 			"version": "2.0.0",
@@ -9341,8 +9300,7 @@
 		"node_modules/js-tokens": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-			"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-			"dev": true
+			"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
 		},
 		"node_modules/js-yaml": {
 			"version": "3.14.1",
@@ -9628,7 +9586,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
 			"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
-			"dev": true,
 			"bin": {
 				"lz-string": "bin/bin.js"
 			}
@@ -10045,7 +10002,6 @@
 			"version": "1.13.1",
 			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
 			"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
-			"dev": true,
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -10054,7 +10010,6 @@
 			"version": "1.1.6",
 			"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
 			"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7",
 				"define-properties": "^1.2.1"
@@ -10070,7 +10025,6 @@
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
 			"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			}
@@ -10079,7 +10033,6 @@
 			"version": "4.1.5",
 			"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
 			"integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.5",
 				"define-properties": "^1.2.1",
@@ -10396,7 +10349,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
 			"integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
-			"dev": true,
 			"engines": {
 				"node": ">= 0.4"
 			}
@@ -10653,7 +10605,6 @@
 			"version": "27.5.1",
 			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
 			"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
-			"dev": true,
 			"dependencies": {
 				"ansi-regex": "^5.0.1",
 				"ansi-styles": "^5.0.0",
@@ -10667,7 +10618,6 @@
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
 			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-			"dev": true,
 			"engines": {
 				"node": ">=10"
 			},
@@ -10909,8 +10859,7 @@
 		"node_modules/react-is": {
 			"version": "17.0.2",
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
-			"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
-			"dev": true
+			"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
 		},
 		"node_modules/read-cache": {
 			"version": "1.0.0",
@@ -10966,7 +10915,6 @@
 			"version": "1.5.2",
 			"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
 			"integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.6",
 				"define-properties": "^1.2.1",
@@ -11237,7 +11185,6 @@
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
 			"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
-			"dev": true,
 			"dependencies": {
 				"define-data-property": "^1.1.2",
 				"es-errors": "^1.3.0",
@@ -11254,7 +11201,6 @@
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
 			"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
-			"dev": true,
 			"dependencies": {
 				"define-data-property": "^1.1.4",
 				"es-errors": "^1.3.0",
@@ -11288,7 +11234,6 @@
 			"version": "1.0.6",
 			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
 			"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
-			"dev": true,
 			"dependencies": {
 				"call-bind": "^1.0.7",
 				"es-errors": "^1.3.0",
@@ -11440,7 +11385,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
 			"integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
-			"dev": true,
 			"dependencies": {
 				"internal-slot": "^1.0.4"
 			},
@@ -11637,7 +11581,6 @@
 			"version": "5.5.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
 			"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-			"dev": true,
 			"dependencies": {
 				"has-flag": "^3.0.0"
 			},
@@ -12643,7 +12586,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
 			"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
-			"dev": true,
 			"dependencies": {
 				"is-bigint": "^1.0.1",
 				"is-boolean-object": "^1.1.0",
@@ -12659,7 +12601,6 @@
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
 			"integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
-			"dev": true,
 			"dependencies": {
 				"is-map": "^2.0.3",
 				"is-set": "^2.0.3",
@@ -12677,7 +12618,6 @@
 			"version": "1.1.15",
 			"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
 			"integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
-			"dev": true,
 			"dependencies": {
 				"available-typed-arrays": "^1.0.7",
 				"call-bind": "^1.0.7",
diff --git a/frontend/package.json b/frontend/package.json
index 26b3b96..43f8feb 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -44,6 +44,7 @@
 	"dependencies": {
 		"@popperjs/core": "^2.11.8",
 		"@sveltejs/adapter-vercel": "^5.1.0",
+		"@testing-library/user-event": "^14.5.2",
 		"classnames": "^2.5.1",
 		"date-picker-svelte": "^2.11.0",
 		"dotenv": "^16.4.5",
diff --git a/frontend/test/Breadcrumb.test.js b/frontend/test/components/Breadcrumb.test.js
similarity index 92%
rename from frontend/test/Breadcrumb.test.js
rename to frontend/test/components/Breadcrumb.test.js
index c2eefea..0db9b0a 100644
--- a/frontend/test/Breadcrumb.test.js
+++ b/frontend/test/components/Breadcrumb.test.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
-import Breadcrumb from '../src/lib/components/Breadcrumb.svelte';
+import Breadcrumb from '../../src/lib/components/Breadcrumb.svelte';
 
 describe('BreadcrumbComponent', () => {
 	test('renders the home breadcrumb and additional breadcrumb items', () => {
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
new file mode 100644
index 0000000..ab53e24
--- /dev/null
+++ b/frontend/test/components/UnitOverview.test.js
@@ -0,0 +1,114 @@
+/* eslint-disable no-undef */
+import { render, screen } from '@testing-library/svelte';
+import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
+import userEvent from '@testing-library/user-event';
+
+describe('UnitOverview', () => {
+    test('renders the course title and id correctly', () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        expect(screen.getByText('CS101 - Introduction to Computer Science')).toBeInTheDocument();
+    });
+
+    test('toggles unit creation modal visibility', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const createButton = screen.getByText('+ Create new unit');
+        await userEvent.click(createButton);
+        expect(screen.getByText('Create unit')).toBeInTheDocument();
+    });
+
+    test('toggles invitation modal visibility', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const inviteButton = screen.getByText('+ Invite');
+        await userEvent.click(inviteButton);
+        expect(screen.getByText('Invite Users')).toBeInTheDocument();
+    });
+
+    test('create unit form submission', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const createButton = screen.getByText('+ Create new unit');
+        await userEvent.click(createButton);
+
+        await userEvent.type(screen.getByPlaceholderText('title'), 'New Unit');
+        await userEvent.click(screen.getByText('Submit'));
+
+        // Simulating successful unit creation response here
+        expect(screen.queryByText('Unit successfully created!')).toBeInTheDocument();
+    });
+
+    test('inviting users through the form', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const inviteButton = screen.getByText('+ Invite');
+        await userEvent.click(inviteButton);
+
+        await userEvent.type(screen.getByPlaceholderText('example'), 'user1 user2');
+        await userEvent.click(screen.getByText('Submit'));
+
+        // Simulating successful invitation sending here
+        expect(screen.queryByText('Invitations has been sent!')).toBeInTheDocument();
+    });
+
+    test('handling error in form submissions', async () => {
+        const mockData = {
+            course: {
+                id: 'CS101',
+                name: 'Introduction to Computer Science'
+            },
+            role: 'lecturer'
+        };
+
+        render(UnitOverview, { data: mockData });
+
+        const createButton = screen.getByText('+ Create new unit');
+        await userEvent.click(createButton);
+
+        await userEvent.type(screen.getByPlaceholderText('title'), ''); // Empty title to trigger validation error
+        await userEvent.click(screen.getByText('Submit'));
+
+        expect(screen.getByText('Could not create unit')).toBeInTheDocument();
+    });
+});
diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 1e5d5e2..3e162d1 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -1,14 +1,21 @@
 import { defineConfig } from 'vitest/config';
 import { svelte } from '@sveltejs/vite-plugin-svelte';
+import { resolve } from 'path';
 
 export default defineConfig({
-	plugins: [svelte()],
-	test: {
-		globals: true,
-		environment: 'jsdom',
-		setupFiles: ['./test/setupTests.js'],
-		transformMode: {
-			web: [/.[tj]sx?$/, /.svelte$/]
-		}
-	}
-});
+    plugins: [svelte()],
+    resolve: {
+        alias: {
+            $app: resolve(__dirname, './src/lib'),
+			// $env: resolve(__dirname, './src/env')
+        }
+    },
+    test: {
+        globals: true,
+        environment: 'jsdom',
+        setupFiles: ['./test/setupTests.js'],
+        transformMode: {
+            web: [/.[tj]sx?$/, /.svelte$/]
+        }
+    }
+});
\ No newline at end of file
-- 
GitLab


From 86381197a90a6d5602c868b33d42a5ccb546ebfc Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:14:25 +0200
Subject: [PATCH 12/59] todo

---
 frontend/vitest.config.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 3e162d1..160eff8 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -6,8 +6,10 @@ export default defineConfig({
     plugins: [svelte()],
     resolve: {
         alias: {
+			$lib: resolve(__dirname, './src/lib'),
+			// TODO FIX IMPORT UNDER
             $app: resolve(__dirname, './src/lib'),
-			// $env: resolve(__dirname, './src/env')
+			$env: resolve(__dirname, './src/env'),
         }
     },
     test: {
-- 
GitLab


From 169ed981e9e7d2ffc0bef302b4b483d284482347 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:19:32 +0200
Subject: [PATCH 13/59] fix app

---
 frontend/vitest.config.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 160eff8..0f2df0e 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -8,7 +8,7 @@ export default defineConfig({
         alias: {
 			$lib: resolve(__dirname, './src/lib'),
 			// TODO FIX IMPORT UNDER
-            $app: resolve(__dirname, './src/lib'),
+            $app: resolve(__dirname, './node_modules/@sveltejs/kit/src/runtime/app'),
 			$env: resolve(__dirname, './src/env'),
         }
     },
-- 
GitLab


From e4e748fb2baef01e633f976cda89b2e2c0e88d07 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:49:15 +0200
Subject: [PATCH 14/59] mocking

---
 frontend/__mocks__/app.js | 0
 frontend/__mocks__/env.js | 0
 frontend/vitest.config.js | 5 ++---
 3 files changed, 2 insertions(+), 3 deletions(-)
 create mode 100644 frontend/__mocks__/app.js
 create mode 100644 frontend/__mocks__/env.js

diff --git a/frontend/__mocks__/app.js b/frontend/__mocks__/app.js
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/__mocks__/env.js b/frontend/__mocks__/env.js
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 0f2df0e..8406482 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -7,9 +7,8 @@ export default defineConfig({
     resolve: {
         alias: {
 			$lib: resolve(__dirname, './src/lib'),
-			// TODO FIX IMPORT UNDER
-            $app: resolve(__dirname, './node_modules/@sveltejs/kit/src/runtime/app'),
-			$env: resolve(__dirname, './src/env'),
+            $app: resolve(__dirname, './__mocks__/app'),
+            $env: resolve(__dirname, './__mocks__/env'),
         }
     },
     test: {
-- 
GitLab


From 0fadf2622fb74cacc4127470df09cc4c1894f52f Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 13:57:44 +0200
Subject: [PATCH 15/59] mocking

---
 .gitignore                              |  4 ++--
 frontend/.gitignore                     |  5 +----
 frontend/__mocks__/app.js               |  0
 frontend/__mocks__/app/navigation.js    | 17 +++++++++++++++++
 frontend/__mocks__/env.js               |  0
 frontend/__mocks__/env/static/public.js |  6 ++++++
 6 files changed, 26 insertions(+), 6 deletions(-)
 delete mode 100644 frontend/__mocks__/app.js
 create mode 100644 frontend/__mocks__/app/navigation.js
 delete mode 100644 frontend/__mocks__/env.js
 create mode 100644 frontend/__mocks__/env/static/public.js

diff --git a/.gitignore b/.gitignore
index 23c4d93..c7feb33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,9 +108,9 @@ celerybeat.pid
 # Environments
 .env
 .venv
-env/
+
 venv/
-ENV/
+
 env.bak/
 venv.bak/
 .nvmrc
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 61bf367..dbd0e02 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -3,10 +3,7 @@ node_modules
 /build
 /.svelte-kit
 /package
-.env
-.env.*
-!.env.example
-!.env.template
+
 .vercel
 .output
 vite.config.js.timestamp-*
diff --git a/frontend/__mocks__/app.js b/frontend/__mocks__/app.js
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/__mocks__/app/navigation.js b/frontend/__mocks__/app/navigation.js
new file mode 100644
index 0000000..9830089
--- /dev/null
+++ b/frontend/__mocks__/app/navigation.js
@@ -0,0 +1,17 @@
+import { jest } from '@jest/globals';
+
+const goto = jest.fn();
+const invalidate = jest.fn();
+const invalidateAll = jest.fn();
+
+jest.mock('$app/navigation', () => ({
+  goto,
+  invalidate,
+  invalidateAll,
+}));
+
+module.exports = {
+  goto,
+  invalidate,
+  invalidateAll,
+};
\ No newline at end of file
diff --git a/frontend/__mocks__/env.js b/frontend/__mocks__/env.js
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/__mocks__/env/static/public.js b/frontend/__mocks__/env/static/public.js
new file mode 100644
index 0000000..bf9c454
--- /dev/null
+++ b/frontend/__mocks__/env/static/public.js
@@ -0,0 +1,6 @@
+
+const PUBLIC_API_URL = 'http://localhost:3000/api';
+
+module.exports = {
+    PUBLIC_API_URL
+  };
\ No newline at end of file
-- 
GitLab


From a237bc597b698f1246e1629760299d5385432f0d Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:02:35 +0200
Subject: [PATCH 16/59] mocking

---
 frontend/__mocks__/app/navigation.js          |  14 +--
 frontend/test/components/UnitOverview.test.js | 116 +-----------------
 2 files changed, 10 insertions(+), 120 deletions(-)

diff --git a/frontend/__mocks__/app/navigation.js b/frontend/__mocks__/app/navigation.js
index 9830089..778cb06 100644
--- a/frontend/__mocks__/app/navigation.js
+++ b/frontend/__mocks__/app/navigation.js
@@ -1,14 +1,8 @@
-import { jest } from '@jest/globals';
+import { vi } from "vitest";
 
-const goto = jest.fn();
-const invalidate = jest.fn();
-const invalidateAll = jest.fn();
-
-jest.mock('$app/navigation', () => ({
-  goto,
-  invalidate,
-  invalidateAll,
-}));
+const goto = vi.fn();
+const invalidate = vi.fn();
+const invalidateAll = vi.fn();
 
 module.exports = {
   goto,
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index ab53e24..a0011c3 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,114 +1,10 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
-import userEvent from '@testing-library/user-event';
 
-describe('UnitOverview', () => {
-    test('renders the course title and id correctly', () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        expect(screen.getByText('CS101 - Introduction to Computer Science')).toBeInTheDocument();
-    });
-
-    test('toggles unit creation modal visibility', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const createButton = screen.getByText('+ Create new unit');
-        await userEvent.click(createButton);
-        expect(screen.getByText('Create unit')).toBeInTheDocument();
-    });
-
-    test('toggles invitation modal visibility', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const inviteButton = screen.getByText('+ Invite');
-        await userEvent.click(inviteButton);
-        expect(screen.getByText('Invite Users')).toBeInTheDocument();
-    });
-
-    test('create unit form submission', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const createButton = screen.getByText('+ Create new unit');
-        await userEvent.click(createButton);
-
-        await userEvent.type(screen.getByPlaceholderText('title'), 'New Unit');
-        await userEvent.click(screen.getByText('Submit'));
-
-        // Simulating successful unit creation response here
-        expect(screen.queryByText('Unit successfully created!')).toBeInTheDocument();
-    });
-
-    test('inviting users through the form', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const inviteButton = screen.getByText('+ Invite');
-        await userEvent.click(inviteButton);
-
-        await userEvent.type(screen.getByPlaceholderText('example'), 'user1 user2');
-        await userEvent.click(screen.getByText('Submit'));
-
-        // Simulating successful invitation sending here
-        expect(screen.queryByText('Invitations has been sent!')).toBeInTheDocument();
-    });
-
-    test('handling error in form submissions', async () => {
-        const mockData = {
-            course: {
-                id: 'CS101',
-                name: 'Introduction to Computer Science'
-            },
-            role: 'lecturer'
-        };
-
-        render(UnitOverview, { data: mockData });
-
-        const createButton = screen.getByText('+ Create new unit');
-        await userEvent.click(createButton);
-
-        await userEvent.type(screen.getByPlaceholderText('title'), ''); // Empty title to trigger validation error
-        await userEvent.click(screen.getByText('Submit'));
-
-        expect(screen.getByText('Could not create unit')).toBeInTheDocument();
-    });
-});
+test('renders UnitOverview component', () => {
+    render(UnitOverview);
+    const linkElement = screen.getByText(/Unit Overview/i);
+    expect(linkElement).toBeInTheDocument();
+    }
+);
\ No newline at end of file
-- 
GitLab


From 78ddd119cd8e8040cdc30921e8f8a4cf87b6276e Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:12:33 +0200
Subject: [PATCH 17/59] Mocking

---
 frontend/__mocks__/Data.js                    | 156 ++++++++++++++++++
 frontend/test/components/UnitOverview.test.js |   7 +-
 2 files changed, 159 insertions(+), 4 deletions(-)
 create mode 100644 frontend/__mocks__/Data.js

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
new file mode 100644
index 0000000..8fc7f2a
--- /dev/null
+++ b/frontend/__mocks__/Data.js
@@ -0,0 +1,156 @@
+const mockData = [
+    {
+        user: {
+            uid: 'test',
+            email: 'test@ntnu.no',
+            enrollments: [
+                {
+                    course_id: 'TDT4000',
+                    course_semester: 'spring2024',
+                    role: 'lecturer',
+                    uid: 'test',
+                    missingUnits: []
+                },
+                {
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023',
+                    role: 'student',
+                    uid: 'test',
+                    missingUnits: [
+                        {
+                            id: 1,
+                            date: '2022-08-23'
+                        },
+                        {
+                            id: 2,
+                            date: '2022-08-30'
+                        }
+                    ]
+                }
+            ],
+            reflections: [],
+            admin: true
+        },
+        course: {
+            id: 'TDT4100',
+            semester: 'fall2023',
+            name: 'Informasjonsteknologi grunnkurs',
+            responsible: '',
+            website: '',
+            questions: [
+                {
+                    id: 1,
+                    question: 'Teaching',
+                    comment: 'What was your best learning success in this unit? Why?'
+                },
+                {
+                    id: 2,
+                    question: 'Difficult',
+                    comment: 'What was your least understood concept in this unit? Why?'
+                }
+            ],
+            users: [
+                {
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023',
+                    role: 'student'
+                }
+            ],
+            reports: [
+                {
+                    report_content: [],
+                    number_of_answers: 0,
+                    unit_id: 1,
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023'
+                },
+                {
+                    report_content: [],
+                    number_of_answers: 0,
+                    unit_id: 2,
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023'
+                },
+                {
+                    report_content: [],
+                    number_of_answers: 0,
+                    unit_id: 3,
+                    course_id: 'TDT4100',
+                    course_semester: 'fall2023'
+                }
+            ]
+        },
+        course_name: 'TDT4100',
+        role: 'student',
+        units: [
+            {
+                hidden: false,
+                title: 'State Machines',
+                date_available: '2022-08-23',
+                course_id: 'TDT4100',
+                course_semester: 'fall2023',
+                id: 1,
+                reflections: [],
+                unit_number: 1
+            },
+            {
+                hidden: false,
+                title: 'HTTP og JSON',
+                date_available: '2022-08-30',
+                course_id: 'TDT4100',
+                course_semester: 'fall2023',
+                id: 2,
+                reflections: [],
+                unit_number: 2
+            },
+            {
+                hidden: false,
+                title: 'MQTT Chat',
+                date_available: '2024-09-07',
+                course_id: 'TDT4100',
+                course_semester: 'fall2023',
+                id: 3,
+                reflections: [],
+                unit_number: 3
+            }
+        ]
+    }
+];
+
+const mockUnits = [
+    {
+        hidden: false,
+        title: 'State Machines',
+        date_available: '2022-08-23',
+        course_id: 'TDT4100',
+        course_semester: 'fall2023',
+        id: 1,
+        reflections: [],
+        unit_number: 1
+    },
+    {
+        hidden: false,
+        title: 'HTTP og JSON',
+        date_available: '2022-08-30',
+        course_id: 'TDT4100',
+        course_semester: 'fall2023',
+        id: 2,
+        reflections: [],
+        unit_number: 2
+    },
+    {
+        hidden: false,
+        title: 'MQTT Chat',
+        date_available: '2024-09-07',
+        course_id: 'TDT4100',
+        course_semester: 'fall2023',
+        id: 3,
+        reflections: [],
+        unit_number: 3
+    }
+];
+
+module.exports = {
+    mockData,
+    mockUnits
+};
\ No newline at end of file
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index a0011c3..ccfe68a 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,10 +1,9 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
+import { render } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
+import { mockData, mockUnits } from '../../__mocks__/Data';
 
 test('renders UnitOverview component', () => {
-    render(UnitOverview);
-    const linkElement = screen.getByText(/Unit Overview/i);
-    expect(linkElement).toBeInTheDocument();
+    render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
     }
 );
\ No newline at end of file
-- 
GitLab


From ae1647e4f9714c9a45495e81663a538d38bb5b2b Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:13:52 +0200
Subject: [PATCH 18/59] Lint

---
 frontend/__mocks__/Data.js                    | 298 +++++++++---------
 frontend/__mocks__/app/navigation.js          |  10 +-
 frontend/__mocks__/env/static/public.js       |   5 +-
 frontend/test/components/UnitOverview.test.js |   5 +-
 frontend/vitest.config.js                     |  32 +-
 5 files changed, 174 insertions(+), 176 deletions(-)

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 8fc7f2a..3a1506a 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -1,156 +1,156 @@
 const mockData = [
-    {
-        user: {
-            uid: 'test',
-            email: 'test@ntnu.no',
-            enrollments: [
-                {
-                    course_id: 'TDT4000',
-                    course_semester: 'spring2024',
-                    role: 'lecturer',
-                    uid: 'test',
-                    missingUnits: []
-                },
-                {
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023',
-                    role: 'student',
-                    uid: 'test',
-                    missingUnits: [
-                        {
-                            id: 1,
-                            date: '2022-08-23'
-                        },
-                        {
-                            id: 2,
-                            date: '2022-08-30'
-                        }
-                    ]
-                }
-            ],
-            reflections: [],
-            admin: true
-        },
-        course: {
-            id: 'TDT4100',
-            semester: 'fall2023',
-            name: 'Informasjonsteknologi grunnkurs',
-            responsible: '',
-            website: '',
-            questions: [
-                {
-                    id: 1,
-                    question: 'Teaching',
-                    comment: 'What was your best learning success in this unit? Why?'
-                },
-                {
-                    id: 2,
-                    question: 'Difficult',
-                    comment: 'What was your least understood concept in this unit? Why?'
-                }
-            ],
-            users: [
-                {
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023',
-                    role: 'student'
-                }
-            ],
-            reports: [
-                {
-                    report_content: [],
-                    number_of_answers: 0,
-                    unit_id: 1,
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023'
-                },
-                {
-                    report_content: [],
-                    number_of_answers: 0,
-                    unit_id: 2,
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023'
-                },
-                {
-                    report_content: [],
-                    number_of_answers: 0,
-                    unit_id: 3,
-                    course_id: 'TDT4100',
-                    course_semester: 'fall2023'
-                }
-            ]
-        },
-        course_name: 'TDT4100',
-        role: 'student',
-        units: [
-            {
-                hidden: false,
-                title: 'State Machines',
-                date_available: '2022-08-23',
-                course_id: 'TDT4100',
-                course_semester: 'fall2023',
-                id: 1,
-                reflections: [],
-                unit_number: 1
-            },
-            {
-                hidden: false,
-                title: 'HTTP og JSON',
-                date_available: '2022-08-30',
-                course_id: 'TDT4100',
-                course_semester: 'fall2023',
-                id: 2,
-                reflections: [],
-                unit_number: 2
-            },
-            {
-                hidden: false,
-                title: 'MQTT Chat',
-                date_available: '2024-09-07',
-                course_id: 'TDT4100',
-                course_semester: 'fall2023',
-                id: 3,
-                reflections: [],
-                unit_number: 3
-            }
-        ]
-    }
+	{
+		user: {
+			uid: 'test',
+			email: 'test@ntnu.no',
+			enrollments: [
+				{
+					course_id: 'TDT4000',
+					course_semester: 'spring2024',
+					role: 'lecturer',
+					uid: 'test',
+					missingUnits: []
+				},
+				{
+					course_id: 'TDT4100',
+					course_semester: 'fall2023',
+					role: 'student',
+					uid: 'test',
+					missingUnits: [
+						{
+							id: 1,
+							date: '2022-08-23'
+						},
+						{
+							id: 2,
+							date: '2022-08-30'
+						}
+					]
+				}
+			],
+			reflections: [],
+			admin: true
+		},
+		course: {
+			id: 'TDT4100',
+			semester: 'fall2023',
+			name: 'Informasjonsteknologi grunnkurs',
+			responsible: '',
+			website: '',
+			questions: [
+				{
+					id: 1,
+					question: 'Teaching',
+					comment: 'What was your best learning success in this unit? Why?'
+				},
+				{
+					id: 2,
+					question: 'Difficult',
+					comment: 'What was your least understood concept in this unit? Why?'
+				}
+			],
+			users: [
+				{
+					course_id: 'TDT4100',
+					course_semester: 'fall2023',
+					role: 'student'
+				}
+			],
+			reports: [
+				{
+					report_content: [],
+					number_of_answers: 0,
+					unit_id: 1,
+					course_id: 'TDT4100',
+					course_semester: 'fall2023'
+				},
+				{
+					report_content: [],
+					number_of_answers: 0,
+					unit_id: 2,
+					course_id: 'TDT4100',
+					course_semester: 'fall2023'
+				},
+				{
+					report_content: [],
+					number_of_answers: 0,
+					unit_id: 3,
+					course_id: 'TDT4100',
+					course_semester: 'fall2023'
+				}
+			]
+		},
+		course_name: 'TDT4100',
+		role: 'student',
+		units: [
+			{
+				hidden: false,
+				title: 'State Machines',
+				date_available: '2022-08-23',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 1,
+				reflections: [],
+				unit_number: 1
+			},
+			{
+				hidden: false,
+				title: 'HTTP og JSON',
+				date_available: '2022-08-30',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 2,
+				reflections: [],
+				unit_number: 2
+			},
+			{
+				hidden: false,
+				title: 'MQTT Chat',
+				date_available: '2024-09-07',
+				course_id: 'TDT4100',
+				course_semester: 'fall2023',
+				id: 3,
+				reflections: [],
+				unit_number: 3
+			}
+		]
+	}
 ];
 
 const mockUnits = [
-    {
-        hidden: false,
-        title: 'State Machines',
-        date_available: '2022-08-23',
-        course_id: 'TDT4100',
-        course_semester: 'fall2023',
-        id: 1,
-        reflections: [],
-        unit_number: 1
-    },
-    {
-        hidden: false,
-        title: 'HTTP og JSON',
-        date_available: '2022-08-30',
-        course_id: 'TDT4100',
-        course_semester: 'fall2023',
-        id: 2,
-        reflections: [],
-        unit_number: 2
-    },
-    {
-        hidden: false,
-        title: 'MQTT Chat',
-        date_available: '2024-09-07',
-        course_id: 'TDT4100',
-        course_semester: 'fall2023',
-        id: 3,
-        reflections: [],
-        unit_number: 3
-    }
+	{
+		hidden: false,
+		title: 'State Machines',
+		date_available: '2022-08-23',
+		course_id: 'TDT4100',
+		course_semester: 'fall2023',
+		id: 1,
+		reflections: [],
+		unit_number: 1
+	},
+	{
+		hidden: false,
+		title: 'HTTP og JSON',
+		date_available: '2022-08-30',
+		course_id: 'TDT4100',
+		course_semester: 'fall2023',
+		id: 2,
+		reflections: [],
+		unit_number: 2
+	},
+	{
+		hidden: false,
+		title: 'MQTT Chat',
+		date_available: '2024-09-07',
+		course_id: 'TDT4100',
+		course_semester: 'fall2023',
+		id: 3,
+		reflections: [],
+		unit_number: 3
+	}
 ];
 
 module.exports = {
-    mockData,
-    mockUnits
-};
\ No newline at end of file
+	mockData,
+	mockUnits
+};
diff --git a/frontend/__mocks__/app/navigation.js b/frontend/__mocks__/app/navigation.js
index 778cb06..0019ff5 100644
--- a/frontend/__mocks__/app/navigation.js
+++ b/frontend/__mocks__/app/navigation.js
@@ -1,11 +1,11 @@
-import { vi } from "vitest";
+import { vi } from 'vitest';
 
 const goto = vi.fn();
 const invalidate = vi.fn();
 const invalidateAll = vi.fn();
 
 module.exports = {
-  goto,
-  invalidate,
-  invalidateAll,
-};
\ No newline at end of file
+	goto,
+	invalidate,
+	invalidateAll
+};
diff --git a/frontend/__mocks__/env/static/public.js b/frontend/__mocks__/env/static/public.js
index bf9c454..c69636e 100644
--- a/frontend/__mocks__/env/static/public.js
+++ b/frontend/__mocks__/env/static/public.js
@@ -1,6 +1,5 @@
-
 const PUBLIC_API_URL = 'http://localhost:3000/api';
 
 module.exports = {
-    PUBLIC_API_URL
-  };
\ No newline at end of file
+	PUBLIC_API_URL
+};
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index ccfe68a..cb75431 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -4,6 +4,5 @@ import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
 import { mockData, mockUnits } from '../../__mocks__/Data';
 
 test('renders UnitOverview component', () => {
-    render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
-    }
-);
\ No newline at end of file
+	render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
+});
diff --git a/frontend/vitest.config.js b/frontend/vitest.config.js
index 8406482..b8c4f59 100644
--- a/frontend/vitest.config.js
+++ b/frontend/vitest.config.js
@@ -3,20 +3,20 @@ import { svelte } from '@sveltejs/vite-plugin-svelte';
 import { resolve } from 'path';
 
 export default defineConfig({
-    plugins: [svelte()],
-    resolve: {
-        alias: {
+	plugins: [svelte()],
+	resolve: {
+		alias: {
 			$lib: resolve(__dirname, './src/lib'),
-            $app: resolve(__dirname, './__mocks__/app'),
-            $env: resolve(__dirname, './__mocks__/env'),
-        }
-    },
-    test: {
-        globals: true,
-        environment: 'jsdom',
-        setupFiles: ['./test/setupTests.js'],
-        transformMode: {
-            web: [/.[tj]sx?$/, /.svelte$/]
-        }
-    }
-});
\ No newline at end of file
+			$app: resolve(__dirname, './__mocks__/app'),
+			$env: resolve(__dirname, './__mocks__/env')
+		}
+	},
+	test: {
+		globals: true,
+		environment: 'jsdom',
+		setupFiles: ['./test/setupTests.js'],
+		transformMode: {
+			web: [/.[tj]sx?$/, /.svelte$/]
+		}
+	}
+});
-- 
GitLab


From 1d9c1ab7b12b7a66acb61eab39c65ee24ddcf8e9 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:14:27 +0200
Subject: [PATCH 19/59] pipeline

---
 .gitlab-ci.yml | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 61cd8b0..5e29931 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -93,6 +93,17 @@ test backend:
   only:
     - merge_requests
 
+test frontend:
+  stage: test
+  image: node:20
+  script:
+    - echo "Testing the frontend"
+    - cd frontend
+    - npm i --cache .npm --prefer-offline
+    - npm run test
+  only:
+    - merge_requests
+
 deploy-frontend:
   stage: deploy
   image: node:19
-- 
GitLab


From 981a31a65a25b92924fa419eee2c67b7112e4fd6 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 16 Apr 2024 14:33:51 +0200
Subject: [PATCH 20/59] mock

---
 frontend/__mocks__/app/environment.js | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 frontend/__mocks__/app/environment.js

diff --git a/frontend/__mocks__/app/environment.js b/frontend/__mocks__/app/environment.js
new file mode 100644
index 0000000..a3c6967
--- /dev/null
+++ b/frontend/__mocks__/app/environment.js
@@ -0,0 +1,7 @@
+import { vi } from 'vitest';
+
+const browser = vi.fn();
+
+module.exports = {
+	browser
+};
-- 
GitLab


From 74df17e844e0cbfa26ef0e01012acff48c47d01d Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Tue, 16 Apr 2024 14:49:25 +0200
Subject: [PATCH 21/59] wip: implementation of some component tests

---
 frontend/test/components/CourseCards.test.js  |   2 +-
 .../test/components/CourseOverview.test.js    | 154 +-----------------
 .../components/CourseOverviewLecturer.test.js | 154 +-----------------
 .../components/CourseOverviewStudent.test.js  |  14 ++
 frontend/test/components/UnitOverview.test.js |   2 +-
 5 files changed, 20 insertions(+), 306 deletions(-)
 create mode 100644 frontend/test/components/CourseOverviewStudent.test.js

diff --git a/frontend/test/components/CourseCards.test.js b/frontend/test/components/CourseCards.test.js
index e84348a..a9dfc8a 100644
--- a/frontend/test/components/CourseCards.test.js
+++ b/frontend/test/components/CourseCards.test.js
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/svelte';
 import CourseCards from '../../src/lib/components/CourseCards.svelte';
 
 describe('CourseCardsComponent', () => {
-	test('renders the course card component correctly', () => {
+	test('Renders the course card component correctly', () => {
 		const mockCourse = [
 			[
 				{
diff --git a/frontend/test/components/CourseOverview.test.js b/frontend/test/components/CourseOverview.test.js
index 91101d6..5bec780 100644
--- a/frontend/test/components/CourseOverview.test.js
+++ b/frontend/test/components/CourseOverview.test.js
@@ -1,162 +1,12 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import CourseOverview from '../../src/lib/components/CourseOverview.svelte';
+import { mockData, mockUnits } from '../../__mocks__/Data';
 
 describe('CourseOverviewComponent', () => {
 	test('renders the course overview component correctly for students', () => {
-		const mockData = [
-			{
-				user: {
-					uid: 'test',
-					email: 'test@ntnu.no',
-					enrollments: [
-						{
-							course_id: 'TDT4000',
-							course_semester: 'spring2024',
-							role: 'lecturer',
-							uid: 'test',
-							missingUnits: []
-						},
-						{
-							course_id: 'TDT4100',
-							course_semester: 'fall2023',
-							role: 'student',
-							uid: 'test',
-							missingUnits: [
-								{
-									id: 1,
-									date: '2022-08-23'
-								},
-								{
-									id: 2,
-									date: '2022-08-30'
-								}
-							]
-						}
-					],
-					reflections: [],
-					admin: true
-				},
-				course: {
-					id: 'TDT4100',
-					semester: 'fall2023',
-					name: 'Informasjonsteknologi grunnkurs',
-					responsible: '',
-					website: '',
-					questions: [
-						{
-							id: 1,
-							question: 'Teaching',
-							comment: 'What was your best learning success in this unit? Why?'
-						},
-						{
-							id: 2,
-							question: 'Difficult',
-							comment: 'What was your least understood concept in this unit? Why?'
-						}
-					],
-					users: [
-						{
-							course_id: 'TDT4100',
-							course_semester: 'fall2023',
-							role: 'student'
-						}
-					],
-					reports: [
-						{
-							report_content: [],
-							number_of_answers: 0,
-							unit_id: 1,
-							course_id: 'TDT4100',
-							course_semester: 'fall2023'
-						},
-						{
-							report_content: [],
-							number_of_answers: 0,
-							unit_id: 2,
-							course_id: 'TDT4100',
-							course_semester: 'fall2023'
-						},
-						{
-							report_content: [],
-							number_of_answers: 0,
-							unit_id: 3,
-							course_id: 'TDT4100',
-							course_semester: 'fall2023'
-						}
-					]
-				},
-				course_name: 'TDT4100',
-				role: 'student',
-				units: [
-					{
-						hidden: false,
-						title: 'State Machines',
-						date_available: '2022-08-23',
-						course_id: 'TDT4100',
-						course_semester: 'fall2023',
-						id: 1,
-						reflections: [],
-						unit_number: 1
-					},
-					{
-						hidden: false,
-						title: 'HTTP og JSON',
-						date_available: '2022-08-30',
-						course_id: 'TDT4100',
-						course_semester: 'fall2023',
-						id: 2,
-						reflections: [],
-						unit_number: 2
-					},
-					{
-						hidden: false,
-						title: 'MQTT Chat',
-						date_available: '2024-09-07',
-						course_id: 'TDT4100',
-						course_semester: 'fall2023',
-						id: 3,
-						reflections: [],
-						unit_number: 3
-					}
-				]
-			}
-		];
-
-		const mockUnits = [
-			{
-				hidden: false,
-				title: 'State Machines',
-				date_available: '2022-08-23',
-				course_id: 'TDT4100',
-				course_semester: 'fall2023',
-				id: 1,
-				reflections: [],
-				unit_number: 1
-			},
-			{
-				hidden: false,
-				title: 'HTTP og JSON',
-				date_available: '2022-08-30',
-				course_id: 'TDT4100',
-				course_semester: 'fall2023',
-				id: 2,
-				reflections: [],
-				unit_number: 2
-			},
-			{
-				hidden: false,
-				title: 'MQTT Chat',
-				date_available: '2024-09-07',
-				course_id: 'TDT4100',
-				course_semester: 'fall2023',
-				id: 3,
-				reflections: [],
-				unit_number: 3
-			}
-		];
 		//Render as student
-		render(CourseOverview, { data: mockData, units: mockUnits, role: 'student' });
+		render(CourseOverview, { data: mockData[0], units: mockUnits[0], role: 'student' });
 
 		//Check base render course overview component, name is static for both lecturer and student
 		expect(screen.getByText('State Machines')).toBeInTheDocument();
diff --git a/frontend/test/components/CourseOverviewLecturer.test.js b/frontend/test/components/CourseOverviewLecturer.test.js
index 2a54b61..88b3c05 100644
--- a/frontend/test/components/CourseOverviewLecturer.test.js
+++ b/frontend/test/components/CourseOverviewLecturer.test.js
@@ -1,162 +1,12 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import CourseOverviewLecturer from '../../src/lib/components/CourseOverviewLecturer.svelte';
+import { mockData, mockUnits } from '../../__mocks__/Data';
 
 describe('CourseOverviewLecturer', () => {
 	test('renders the course overview component correctly for lecturers', () => {
-		const mockData = [
-			{
-				user: {
-					uid: 'test',
-					email: 'test@ntnu.no',
-					enrollments: [
-						{
-							course_id: 'TDT4000',
-							course_semester: 'spring2024',
-							role: 'lecturer',
-							uid: 'test',
-							missingUnits: []
-						},
-						{
-							course_id: 'TDT4100',
-							course_semester: 'fall2023',
-							role: 'student',
-							uid: 'test',
-							missingUnits: [
-								{
-									id: 1,
-									date: '2022-08-23'
-								},
-								{
-									id: 2,
-									date: '2022-08-30'
-								}
-							]
-						}
-					],
-					reflections: [],
-					admin: true
-				},
-				course: {
-					id: 'TDT4100',
-					semester: 'fall2023',
-					name: 'Informasjonsteknologi grunnkurs',
-					responsible: '',
-					website: '',
-					questions: [
-						{
-							id: 1,
-							question: 'Teaching',
-							comment: 'What was your best learning success in this unit? Why?'
-						},
-						{
-							id: 2,
-							question: 'Difficult',
-							comment: 'What was your least understood concept in this unit? Why?'
-						}
-					],
-					users: [
-						{
-							course_id: 'TDT4100',
-							course_semester: 'fall2023',
-							role: 'student'
-						}
-					],
-					reports: [
-						{
-							report_content: [],
-							number_of_answers: 0,
-							unit_id: 1,
-							course_id: 'TDT4100',
-							course_semester: 'fall2023'
-						},
-						{
-							report_content: [],
-							number_of_answers: 0,
-							unit_id: 2,
-							course_id: 'TDT4100',
-							course_semester: 'fall2023'
-						},
-						{
-							report_content: [],
-							number_of_answers: 0,
-							unit_id: 3,
-							course_id: 'TDT4100',
-							course_semester: 'fall2023'
-						}
-					]
-				},
-				course_name: 'TDT4100',
-				role: 'student',
-				units: [
-					{
-						hidden: false,
-						title: 'State Machines',
-						date_available: '2022-08-23',
-						course_id: 'TDT4100',
-						course_semester: 'fall2023',
-						id: 1,
-						reflections: [],
-						unit_number: 1
-					},
-					{
-						hidden: false,
-						title: 'HTTP og JSON',
-						date_available: '2022-08-30',
-						course_id: 'TDT4100',
-						course_semester: 'fall2023',
-						id: 2,
-						reflections: [],
-						unit_number: 2
-					},
-					{
-						hidden: false,
-						title: 'MQTT Chat',
-						date_available: '2024-09-07',
-						course_id: 'TDT4100',
-						course_semester: 'fall2023',
-						id: 3,
-						reflections: [],
-						unit_number: 3
-					}
-				]
-			}
-		];
-
-		const mockUnits = [
-			{
-				hidden: false,
-				title: 'State Machines',
-				date_available: '2022-08-23',
-				course_id: 'TDT4100',
-				course_semester: 'fall2023',
-				id: 1,
-				reflections: [],
-				unit_number: 1
-			},
-			{
-				hidden: false,
-				title: 'HTTP og JSON',
-				date_available: '2022-08-30',
-				course_id: 'TDT4100',
-				course_semester: 'fall2023',
-				id: 2,
-				reflections: [],
-				unit_number: 2
-			},
-			{
-				hidden: false,
-				title: 'MQTT Chat',
-				date_available: '2024-09-07',
-				course_id: 'TDT4100',
-				course_semester: 'fall2023',
-				id: 3,
-				reflections: [],
-				unit_number: 3
-			}
-		];
 		//Render as student
-		render(CourseOverviewLecturer, { data: mockData, units: mockUnits, role: 'lecturer' });
+		render(CourseOverviewLecturer, { data: mockData[0], units: mockUnits[0] });
 
 		//Check that create new unit button is present, confirmning lecturer role
 		expect(screen.getByText('Create new unit')).toBeInTheDocument();
diff --git a/frontend/test/components/CourseOverviewStudent.test.js b/frontend/test/components/CourseOverviewStudent.test.js
new file mode 100644
index 0000000..4942679
--- /dev/null
+++ b/frontend/test/components/CourseOverviewStudent.test.js
@@ -0,0 +1,14 @@
+/* eslint-disable no-undef */
+import { render, screen } from '@testing-library/svelte';
+import CourseOverviewStudent from '../../src/lib/components/CourseOverviewStudent.svelte';
+import { mockData, mockUnits } from '../../__mocks__/Data';
+
+describe('CourseOverviewStudent', () => {
+	test('renders the course overview component correctly for lecturers', () => {
+		//Render as student
+		render(CourseOverviewStudent, { data: mockData[0], units: mockUnits[0] });
+
+		//Check that the decline unit button is rendered, confirming student
+		expect(screen.getByText('Decline Reflection')).toBeInTheDocument();
+	});
+});
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index cb75431..599c50f 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -4,5 +4,5 @@ import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
 import { mockData, mockUnits } from '../../__mocks__/Data';
 
 test('renders UnitOverview component', () => {
-	render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
+	render(UnitOverview, { data: mockData[0], units: mockUnits[0], role: 'student' });
 });
-- 
GitLab


From 2fc894e0204695288ce0e6b7a2c3078f7ff00067 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Wed, 17 Apr 2024 12:25:45 +0200
Subject: [PATCH 22/59] UnitOverview conflict fixed

---
 frontend/test/components/UnitOverview.test.js | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index b3ccfde..cb75431 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -4,9 +4,5 @@ import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
 import { mockData, mockUnits } from '../../__mocks__/Data';
 
 test('renders UnitOverview component', () => {
-<<<<<<< HEAD
-	render(UnitOverview, { data: mockData[0], units: mockUnits[0], role: 'student' });
-=======
 	render(UnitOverview, { data: mockData[0], units: mockUnits[0] });
->>>>>>> origin/test-config-file
 });
-- 
GitLab


From 0e8d66337af278f1865630013f9c28b9d4f900ff Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Wed, 17 Apr 2024 14:56:31 +0200
Subject: [PATCH 23/59] WIP component testing, fixed problems running locally

---
 frontend/__mocks__/Data.js                   | 14 +++++++-------
 frontend/__mocks__/env/static/public.js      |  2 +-
 frontend/test/components/CourseCards.test.js | 18 ++++--------------
 3 files changed, 12 insertions(+), 22 deletions(-)

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 8105d2f..57120e1 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -61,13 +61,6 @@ const mockUser = {
 	uid: 'test',
 	email: 'test@ntnu.no',
 	enrollments: [
-		{
-			course_id: 'TDT4000',
-			course_semester: 'spring2024',
-			role: 'lecturer',
-			uid: 'test',
-			missingUnits: []
-		},
 		{
 			course_id: 'TDT4100',
 			course_semester: 'fall2023',
@@ -83,6 +76,13 @@ const mockUser = {
 					date: '2022-08-30'
 				}
 			]
+		},
+		{
+			course_id: 'TDT4000',
+			course_semester: 'spring2024',
+			role: 'lecturer',
+			uid: 'test',
+			missingUnits: []
 		}
 	],
 	reflections: [],
diff --git a/frontend/__mocks__/env/static/public.js b/frontend/__mocks__/env/static/public.js
index c69636e..f78fc91 100644
--- a/frontend/__mocks__/env/static/public.js
+++ b/frontend/__mocks__/env/static/public.js
@@ -1,4 +1,4 @@
-const PUBLIC_API_URL = 'http://localhost:3000/api';
+const PUBLIC_API_URL = 'http://127.0.0.1:8000';
 
 module.exports = {
 	PUBLIC_API_URL
diff --git a/frontend/test/components/CourseCards.test.js b/frontend/test/components/CourseCards.test.js
index a9dfc8a..d8ead26 100644
--- a/frontend/test/components/CourseCards.test.js
+++ b/frontend/test/components/CourseCards.test.js
@@ -1,27 +1,17 @@
 /* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import CourseCards from '../../src/lib/components/CourseCards.svelte';
+import { mockUser } from '../../__mocks__/Data';
 
 describe('CourseCardsComponent', () => {
 	test('Renders the course card component correctly', () => {
-		const mockCourse = [
-			[
-				{
-					course_id: 'TDT0000',
-					course_semester: 'spring2024',
-					role: 'student',
-					uid: 'test',
-					missingUnits: []
-				}
-			]
-		];
-		render(CourseCards, { courses: mockCourse, role: 'student' });
+		render(CourseCards, { courses: mockUser.enrollments, role: 'student' });
 
 		//Check base render of course card, with correct course id
-		expect(screen.getByText('TDT0000')).toBeInTheDocument();
+		expect(screen.getByText('TDT4100')).toBeInTheDocument();
 
 		//Check that the course card displays correct semester
-		expect(screen.getByText('Spring 2024')).toBeInTheDocument();
+		expect(screen.getByText('Fall 2023')).toBeInTheDocument();
 
 		//Check that the course card displays correct badge (student/lecturer)
 		expect(screen.getByText('Student')).toBeInTheDocument();
-- 
GitLab


From 072dc1daeb296b23c6ddef876b2f7360eed18818 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Thu, 18 Apr 2024 11:43:44 +0200
Subject: [PATCH 24/59] add lecturer to mock data

---
 frontend/__mocks__/Data.js | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 57120e1..8b686dc 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -148,9 +148,18 @@ const mockData = [
 		units: mockUnits,
 		unit: mockUnit,
 		unit_id: mockUnitId
+	},
+	{
+		user: mockUser,
+		course: mockCourse,
+		course_name: 'TDT4100',
+		role: 'lecturer',
+		units: mockUnits,
+		unit: mockUnit,
+		unit_id: mockUnitId
 	}
 ];
-module.exports = {
+export {
 	mockData,
 	mockUnits,
 	mockUnit,
-- 
GitLab


From 6103bb8b67434eef7744c35555fec61029e5ad52 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Thu, 18 Apr 2024 12:09:39 +0200
Subject: [PATCH 25/59] add two working DeleteUnitModal tests

---
 .../test/components/DeleteUnitModal.test.js   | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 frontend/test/components/DeleteUnitModal.test.js

diff --git a/frontend/test/components/DeleteUnitModal.test.js b/frontend/test/components/DeleteUnitModal.test.js
new file mode 100644
index 0000000..502c6f0
--- /dev/null
+++ b/frontend/test/components/DeleteUnitModal.test.js
@@ -0,0 +1,26 @@
+/* eslint-disable no-undef */
+import { fireEvent, render, screen } from '@testing-library/svelte';
+import DeleteUnitModal from '../../src/lib/components/DeleteUnitModal.svelte';
+import { mockData } from '../../__mocks__/Data';
+
+describe('DeleteUnitModal', () => {
+	test('The delete unit button is rendered', () => {
+		//Render as student
+
+		render(DeleteUnitModal, { data: mockData[1] });
+
+		//Check that the decline unit button is rendered when clicking the delete unit button
+        expect(screen.getByText(/Delete Unit/i)).toBeInTheDocument();
+
+    })
+    test('The deleteUnitModal is rendered when clicking "delete unit" button', async () => {
+        render(DeleteUnitModal, { data: mockData[1] });
+
+        //Click the delete unit button
+        const deleteBtn = screen.getByText(/Delete Unit/i);
+        await fireEvent.click(deleteBtn);
+        const modalText = /Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i;
+
+		expect(screen.getByText(modalText)).toBeInTheDocument();
+	});
+});
-- 
GitLab


From b4c7924da09204cac8db3059a2a83b7b41352331 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Thu, 18 Apr 2024 12:49:05 +0200
Subject: [PATCH 26/59] Implemented component test for CourseCards

---
 frontend/test/components/CourseCards.test.js | 52 +++++++++++++++-----
 1 file changed, 41 insertions(+), 11 deletions(-)

diff --git a/frontend/test/components/CourseCards.test.js b/frontend/test/components/CourseCards.test.js
index d8ead26..a44be20 100644
--- a/frontend/test/components/CourseCards.test.js
+++ b/frontend/test/components/CourseCards.test.js
@@ -1,19 +1,49 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
+import { render, fireEvent, waitFor } from '@testing-library/svelte';
 import CourseCards from '../../src/lib/components/CourseCards.svelte';
-import { mockUser } from '../../__mocks__/Data';
+import { goto } from '$app/navigation';
 
-describe('CourseCardsComponent', () => {
-	test('Renders the course card component correctly', () => {
-		render(CourseCards, { courses: mockUser.enrollments, role: 'student' });
+describe('CourseCards component', () => {
+	//Confirms that several course cards are rendered correctly
+	it('renders course cards correctly', async () => {
+		const { getByText } = render(CourseCards, {
+			props: {
+				courses: [
+					{ course_id: '1', course_semester: 'fall2021', name: 'Course 1' },
+					{ course_id: '2', course_semester: 'spring2022', name: 'Course 2' }
+				],
+				role: 'Student'
+			}
+		});
 
-		//Check base render of course card, with correct course id
-		expect(screen.getByText('TDT4100')).toBeInTheDocument();
+		await waitFor(() => {
+			expect(getByText('Fall 2021')).toBeInTheDocument();
+			expect(getByText('Spring 2022')).toBeInTheDocument();
+		});
+	});
+
+	//Confirms that the role badge is set and rendered correctly
+	it('renders role badge correctly', async () => {
+		const { getByText } = render(CourseCards, {
+			props: {
+				courses: [{ course_id: '1', course_semester: 'fall2021', name: 'Course 1' }],
+				role: 'Lecturer'
+			}
+		});
+		await waitFor(() => expect(getByText('Lecturer')).toBeInTheDocument());
+	});
 
-		//Check that the course card displays correct semester
-		expect(screen.getByText('Fall 2023')).toBeInTheDocument();
+	//Confirms that the course cards are clickable and navigate to the course view
+	it('navigates to course view on card click', async () => {
+		const { getByText } = render(CourseCards, {
+			props: {
+				courses: [{ course_id: '1', course_semester: 'fall2021', name: 'Course 1' }],
+				role: 'Student'
+			}
+		});
 
-		//Check that the course card displays correct badge (student/lecturer)
-		expect(screen.getByText('Student')).toBeInTheDocument();
+		const card = await waitFor(() => getByText('Fall 2021'));
+		await fireEvent.click(card);
+		expect(goto).toHaveBeenCalledWith('/courseview/fall2021/1');
 	});
 });
-- 
GitLab


From fbe9f6b8b9e8dbcea244dfc5dbb8e5e7674ee3f0 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Thu, 18 Apr 2024 13:51:16 +0200
Subject: [PATCH 27/59] extra test for deleteUnitModal

---
 .../src/lib/components/DeleteUnitModal.svelte  |  6 +++++-
 .../test/components/DeleteUnitModal.test.js    | 18 ++++++++++++++++--
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/frontend/src/lib/components/DeleteUnitModal.svelte b/frontend/src/lib/components/DeleteUnitModal.svelte
index c234394..cc4c6d6 100644
--- a/frontend/src/lib/components/DeleteUnitModal.svelte
+++ b/frontend/src/lib/components/DeleteUnitModal.svelte
@@ -58,7 +58,11 @@
 			Are you sure you want to delete this unit? All the unit data will be deleted permanently.
 		</p>
 		<div class="mt-6 w-full flex space-x-2 justify-center">
-			<Button on:click={deleteUnit} class="w-36 bg-red-500 text-white">
+			<Button
+				data-testid="confirm-delete-button"
+				on:click={deleteUnit}
+				class="w-36 bg-red-500 text-white"
+			>
 				Delete unit
 				<TrashBinOutline class="w-4 h-4 ml-2" />
 			</Button>
diff --git a/frontend/test/components/DeleteUnitModal.test.js b/frontend/test/components/DeleteUnitModal.test.js
index 502c6f0..9419d40 100644
--- a/frontend/test/components/DeleteUnitModal.test.js
+++ b/frontend/test/components/DeleteUnitModal.test.js
@@ -6,7 +6,6 @@ import { mockData } from '../../__mocks__/Data';
 describe('DeleteUnitModal', () => {
 	test('The delete unit button is rendered', () => {
 		//Render as student
-
 		render(DeleteUnitModal, { data: mockData[1] });
 
 		//Check that the decline unit button is rendered when clicking the delete unit button
@@ -19,8 +18,23 @@ describe('DeleteUnitModal', () => {
         //Click the delete unit button
         const deleteBtn = screen.getByText(/Delete Unit/i);
         await fireEvent.click(deleteBtn);
+
+        //Set the expected modal text after clicking the delete unit button
         const modalText = /Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i;
 
 		expect(screen.getByText(modalText)).toBeInTheDocument();
 	});
-});
+    test('The deleteUnitModal is removed when clicking cancel', async () => {
+        render(DeleteUnitModal, { data: mockData[1] });
+
+        //Click the delete unit button to render the modal
+        const deleteBtn = screen.getByText(/Delete Unit/i);
+        await fireEvent.click(deleteBtn);
+        const modalText = screen.getByText(/Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i)
+
+        //Click the cancel button to remove the modal
+        const cancelBtn = screen.getByText(/Cancel/i);
+        await fireEvent.click(cancelBtn)        
+		expect(modalText).not.toBeInTheDocument();
+	});
+})
-- 
GitLab


From 05d92cfbc6562e03eedc6581a2878a94442a3487 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Thu, 18 Apr 2024 14:58:01 +0200
Subject: [PATCH 28/59] Added testing for CourseOverview and
 CourseOverviewLecturer

---
 frontend/__mocks__/Data.js                    |  9 +--
 .../src/lib/components/CourseOverview.svelte  |  4 --
 .../test/components/CourseOverview.test.js    | 38 ++++++++---
 .../components/CourseOverviewLecturer.test.js | 65 ++++++++++++++++---
 4 files changed, 84 insertions(+), 32 deletions(-)

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 8b686dc..d419efd 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -159,11 +159,4 @@ const mockData = [
 		unit_id: mockUnitId
 	}
 ];
-export {
-	mockData,
-	mockUnits,
-	mockUnit,
-	mockUnitId,
-	mockCourse,
-	mockUser
-};
+export { mockData, mockUnits, mockUnit, mockUnitId, mockCourse, mockUser };
diff --git a/frontend/src/lib/components/CourseOverview.svelte b/frontend/src/lib/components/CourseOverview.svelte
index 64abb7e..1f3db47 100644
--- a/frontend/src/lib/components/CourseOverview.svelte
+++ b/frontend/src/lib/components/CourseOverview.svelte
@@ -13,10 +13,6 @@
 		unit.unit_number = unitCounter;
 		unitCounter++;
 	});
-
-	console.log(data);
-	console.log(role);
-	console.log(units);
 </script>
 
 <main class="flex-shrink-0">
diff --git a/frontend/test/components/CourseOverview.test.js b/frontend/test/components/CourseOverview.test.js
index 5bec780..87bc9eb 100644
--- a/frontend/test/components/CourseOverview.test.js
+++ b/frontend/test/components/CourseOverview.test.js
@@ -1,17 +1,35 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
+import { render } from '@testing-library/svelte';
+import { expect } from 'chai';
 import CourseOverview from '../../src/lib/components/CourseOverview.svelte';
-import { mockData, mockUnits } from '../../__mocks__/Data';
+import { mockData } from '../../__mocks__/Data';
 
-describe('CourseOverviewComponent', () => {
-	test('renders the course overview component correctly for students', () => {
-		//Render as student
-		render(CourseOverview, { data: mockData[0], units: mockUnits[0], role: 'student' });
+describe('CourseOverview component', () => {
+	//Correct rendering of CourseOverview as a student
+	it('renders CourseOverviewStudent when role is student', () => {
+		const { getByText } = render(CourseOverview, {
+			props: {
+				data: mockData[0],
+				role: 'student',
+				units: []
+			}
+		});
 
-		//Check base render course overview component, name is static for both lecturer and student
-		expect(screen.getByText('State Machines')).toBeInTheDocument();
+		//Unroll is only rendered for students
+		expect(getByText('Unroll course')).to.exist;
+	});
+
+	//Correct rendering of CourseOverview as a lecturer
+	it('renders CourseOverviewLecturer when role is lecturer', () => {
+		const { getByText } = render(CourseOverview, {
+			props: {
+				data: mockData[1],
+				role: 'lecturer',
+				units: []
+			}
+		});
 
-		//Confirms that the breadcrumb is present in courseoverview
-		expect(screen.getByText('Courses')).toBeInTheDocument();
+		//Create new unit is only rendered for lecturers
+		expect(getByText('Create new unit')).to.exist;
 	});
 });
diff --git a/frontend/test/components/CourseOverviewLecturer.test.js b/frontend/test/components/CourseOverviewLecturer.test.js
index 88b3c05..b524fd8 100644
--- a/frontend/test/components/CourseOverviewLecturer.test.js
+++ b/frontend/test/components/CourseOverviewLecturer.test.js
@@ -1,17 +1,62 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
 import CourseOverviewLecturer from '../../src/lib/components/CourseOverviewLecturer.svelte';
-import { mockData, mockUnits } from '../../__mocks__/Data';
+import { render, fireEvent } from '@testing-library/svelte';
+import { expect } from 'chai';
+import { mockData } from '../../__mocks__/Data';
+import { goto } from '$app/navigation';
 
-describe('CourseOverviewLecturer', () => {
-	test('renders the course overview component correctly for lecturers', () => {
-		//Render as student
-		render(CourseOverviewLecturer, { data: mockData[0], units: mockUnits[0] });
+describe('CourseOverviewLecturer component', () => {
+	//Correct rendering of create new unit button with correct onClick event
+	it('renders "Create new unit" button and fires click event', async () => {
+		const { getByText } = render(CourseOverviewLecturer, {
+			props: {
+				data: mockData[0],
+				units: []
+			}
+		});
 
-		//Check that create new unit button is present, confirmning lecturer role
-		expect(screen.getByText('Create new unit')).toBeInTheDocument();
+		const button = getByText('Create new unit');
+		expect(button).to.exist;
 
-		//Check that the view report button is rendered correctly
-		expect(screen.getByText('View report')).toBeInTheDocument();
+		await fireEvent.click(button);
+
+		//Correct routing to create unit page
+		expect(goto).toHaveBeenCalledWith(
+			`/courseview/${mockData[0].course.semester}/${mockData[0].course.id}/create`,
+			{
+				replaceState: false
+			}
+		);
+	});
+
+	//Correct rendering of page when no units exists yet
+	it('renders "No units exists for this course yet" when no units exist for the course', () => {
+		const { getByText } = render(CourseOverviewLecturer, {
+			props: {
+				data: mockData[0],
+				units: []
+			}
+		});
+
+		//Text rendered when no units exist for the course
+		expect(getByText('No units exists for this course yet')).to.exist;
+	});
+
+	//Correct rendering of several units on page
+	it('renders UnitCardLecturer for each unit and fires click events', async () => {
+		const { getByText } = render(CourseOverviewLecturer, {
+			props: {
+				data: mockData[0],
+				units: mockData[0].units
+			}
+		});
+
+		//Checks that all units are rendered
+		await Promise.all(
+			mockData[0].units.map(async (unit) => {
+				const unitCard = getByText(unit.title);
+				expect(unitCard).to.exist;
+			})
+		);
 	});
 });
-- 
GitLab


From a8da51fff42ca5183fa3fc73ae6a7e268cf0cab5 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Thu, 18 Apr 2024 15:47:30 +0200
Subject: [PATCH 29/59] not working - navbar.test

I am getting an error which is crashing the test and therefore I commented the whole file out
---
 frontend/__mocks__/Data.js                |  3 ++-
 frontend/src/lib/components/Navbar.svelte |  2 +-
 frontend/test/components/Navbar.test.js   | 20 ++++++++++++++++++++
 3 files changed, 23 insertions(+), 2 deletions(-)
 create mode 100644 frontend/test/components/Navbar.test.js

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 8b686dc..f922268 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -86,7 +86,8 @@ const mockUser = {
 		}
 	],
 	reflections: [],
-	admin: true
+	admin: true,
+	detail: "test"
 };
 
 const mockCourse = {
diff --git a/frontend/src/lib/components/Navbar.svelte b/frontend/src/lib/components/Navbar.svelte
index 7d882e2..3fef551 100644
--- a/frontend/src/lib/components/Navbar.svelte
+++ b/frontend/src/lib/components/Navbar.svelte
@@ -99,7 +99,7 @@
 	}
 </script>
 
-{#if user.detail !== 'You are not logged in' && user !== undefined}
+{#if user !== undefined && user.detail !== 'You are not logged in'}
 	<Navbar shadow class="p-6 sm:px-3 md:px-15 dark:bg-gray-800">
 		<NavBrand href="/overview">
 			<img
diff --git a/frontend/test/components/Navbar.test.js b/frontend/test/components/Navbar.test.js
new file mode 100644
index 0000000..8c82823
--- /dev/null
+++ b/frontend/test/components/Navbar.test.js
@@ -0,0 +1,20 @@
+// /* eslint-disable no-undef */
+
+// import { fireEvent, render, screen } from '@testing-library/svelte';
+// import Navbar from '../../src/lib/components/Navbar.svelte';
+// import { mockUser } from '../../__mocks__/Data';
+
+// describe('Navbar', () => {
+//     test('the navbar is rendered', () => {
+//         const testUser = mockUser;
+//         console.log("user is:", testUser); // Verifying the mock user data
+        
+//         // Render with testuser
+//         render(Navbar, { user: testUser });
+
+//         //TODO figure out the error TypeError: window.matchMedia is not a function, as this crashing the test
+
+
+//         expect(screen.getByText(/Logged in as/i)).toBeInTheDocument();
+//     });
+// });
\ No newline at end of file
-- 
GitLab


From 17f38da406cc0a2c7668cbad27b3207c0e74f4b1 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Thu, 18 Apr 2024 23:55:53 +0200
Subject: [PATCH 30/59] add some testing to unitoverview

---
 frontend/__mocks__/Data.js                    | 42 ++++++++++++++++-
 frontend/test/components/UnitOverview.test.js | 45 ++++++++++++++++---
 2 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index f90f146..0e684fe 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -90,6 +90,46 @@ const mockUser = {
 	detail: "test"
 };
 
+const mockLecturerUser = {
+	uid: 'test',
+	email: 'test@ntnu.no',
+	enrollments: [
+		{
+			course_id: 'TDT4100',
+			course_semester: 'fall2023',
+			role: 'lecturer',
+			uid: 'test',
+			missingUnits: [
+				{
+					id: 1,
+					date: '2025-08-23'
+				},
+				{
+					id: 2,
+					date: '2025-08-30'
+				}
+			]
+		},
+		{
+			course_id: 'TDT4000',
+			course_semester: 'spring2024',
+			role: 'lecturer',
+			uid: 'test',
+			missingUnits: [	{
+				id: 1,
+				date: '2025-08-23'
+			},
+			{
+				id: 2,
+				date: '2025-08-30'
+			}]
+		}
+	],
+	reflections: [],
+	admin: true,
+	detail: "test"
+};
+
 const mockCourse = {
 	id: 'TDT4100',
 	semester: 'fall2023',
@@ -160,4 +200,4 @@ const mockData = [
 		unit_id: mockUnitId
 	}
 ];
-export { mockData, mockUnits, mockUnit, mockUnitId, mockCourse, mockUser };
+export { mockData, mockUnits, mockUnit, mockUnitId, mockCourse, mockUser, mockLecturerUser };
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index 8e15c7c..1cc9051 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,8 +1,43 @@
 /* eslint-disable no-undef */
-import { render } from '@testing-library/svelte';
+import { render, screen } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
-import { mockData } from '../../__mocks__/Data';
+import { mockData, mockLecturerUser } from '../../__mocks__/Data';
 
-test('renders UnitOverview component', () => {
-	render(UnitOverview, { data: mockData[0] });
-});
+describe('UnitOverview', () => {
+	test('A student user renders the student view', () => {
+		render(UnitOverview, { data: mockData[0] });
+
+			// Text which only shows up for the student 
+			expect(screen.queryByText(/Write your reflection for this unit. Make sure not to include any sensitive or private information./i)).not.toBeNull();
+	});
+	test('A lecturer user renders the lecturer view', () => {
+		const lecturerData = {
+			user: mockLecturerUser,
+			course: mockData[1].course,
+			unit: {
+				unit: mockData[1]
+			}, 
+			role: 'lecturer'
+		};
+		lecturerData.course.users = {
+			course_id: 'TDT4100',
+			course_semester: 'fall2023',
+			role: 'lecturer'
+		}
+
+		render(UnitOverview, {  data: lecturerData });
+
+		// Text which only shows up for the lecturer
+		expect(screen.queryByText(/The name of the unit, visible to the students./i)).not.toBeNull();
+	});
+
+	test('A future unit cannot be reflected on', () => {
+		const alteredData = mockData[0];
+		alteredData.unit.unit.date_available = '3000-04-12'
+		console.log(alteredData.unit.unit.date_available)
+		render(UnitOverview, { data: alteredData });
+			// Text which only shows up when a unit is in the future
+			expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
+	});
+
+})
-- 
GitLab


From 6a97d4124d5ef0a3ba7028c33a2a447a555c5550 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Thu, 18 Apr 2024 23:58:16 +0200
Subject: [PATCH 31/59] change when isUserLecturer is set

---
 frontend/src/lib/components/UnitOverview.svelte | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/frontend/src/lib/components/UnitOverview.svelte b/frontend/src/lib/components/UnitOverview.svelte
index f3ab08f..a6ada81 100644
--- a/frontend/src/lib/components/UnitOverview.svelte
+++ b/frontend/src/lib/components/UnitOverview.svelte
@@ -16,7 +16,7 @@
 
 	let availableDate = data.unit.unit.date_available;
 	let isUnitOngoing: boolean = false;
-	let isUserLecturer: boolean = false;
+	let isUserLecturer: boolean = data.role === 'lecturer';
 	let decline: boolean = false;
 	let answers: string[] = [];
 
@@ -79,7 +79,6 @@
 		const availableDate = new Date(data.unit.unit.date_available);
 		const today = new Date();
 		isUnitOngoing = availableDate <= today;
-		isUserLecturer = data.role === 'lecturer'; //Sets the value to true if the user is a lecturer
 		decline = false;
 	});
 
-- 
GitLab


From 612067facd1c30447c084d6502edd841ff21f830 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Fri, 19 Apr 2024 12:12:33 +0200
Subject: [PATCH 32/59] Added testing for CourseOverviewStudent

---
 .../test/components/CourseOverview.test.js    |  1 -
 .../components/CourseOverviewLecturer.test.js |  1 -
 .../components/CourseOverviewStudent.test.js  | 60 ++++++++++++++++---
 3 files changed, 52 insertions(+), 10 deletions(-)

diff --git a/frontend/test/components/CourseOverview.test.js b/frontend/test/components/CourseOverview.test.js
index 87bc9eb..75224e4 100644
--- a/frontend/test/components/CourseOverview.test.js
+++ b/frontend/test/components/CourseOverview.test.js
@@ -1,6 +1,5 @@
 /* eslint-disable no-undef */
 import { render } from '@testing-library/svelte';
-import { expect } from 'chai';
 import CourseOverview from '../../src/lib/components/CourseOverview.svelte';
 import { mockData } from '../../__mocks__/Data';
 
diff --git a/frontend/test/components/CourseOverviewLecturer.test.js b/frontend/test/components/CourseOverviewLecturer.test.js
index b524fd8..716db17 100644
--- a/frontend/test/components/CourseOverviewLecturer.test.js
+++ b/frontend/test/components/CourseOverviewLecturer.test.js
@@ -1,7 +1,6 @@
 /* eslint-disable no-undef */
 import CourseOverviewLecturer from '../../src/lib/components/CourseOverviewLecturer.svelte';
 import { render, fireEvent } from '@testing-library/svelte';
-import { expect } from 'chai';
 import { mockData } from '../../__mocks__/Data';
 import { goto } from '$app/navigation';
 
diff --git a/frontend/test/components/CourseOverviewStudent.test.js b/frontend/test/components/CourseOverviewStudent.test.js
index 4942679..4bb6cd1 100644
--- a/frontend/test/components/CourseOverviewStudent.test.js
+++ b/frontend/test/components/CourseOverviewStudent.test.js
@@ -1,14 +1,58 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
+import { render, fireEvent } from '@testing-library/svelte';
 import CourseOverviewStudent from '../../src/lib/components/CourseOverviewStudent.svelte';
-import { mockData, mockUnits } from '../../__mocks__/Data';
+import { mockData } from '../../__mocks__/Data';
 
-describe('CourseOverviewStudent', () => {
-	test('renders the course overview component correctly for lecturers', () => {
-		//Render as student
-		render(CourseOverviewStudent, { data: mockData[0], units: mockUnits[0] });
+describe('CourseOverviewStudent component', () => {
+	//Correctly renders helper text
+	it('renders "No units available yet for this course" when no units exist', () => {
+		const { getByText } = render(CourseOverviewStudent, {
+			props: {
+				data: mockData[0],
+				units: []
+			}
+		});
 
-		//Check that the decline unit button is rendered, confirming student
-		expect(screen.getByText('Decline Reflection')).toBeInTheDocument();
+		expect(getByText('No units available yet for this course')).to.exist;
+	});
+
+	//Correctly renders show/hide button, and updates accordingly based on click event
+	it('renders "Show finished and unavailable units" button and fires click event', async () => {
+		const { getByText } = render(CourseOverviewStudent, {
+			props: {
+				data: mockData[0],
+				units: mockData[0].units
+			}
+		});
+
+		const button = getByText('Show finished and unavailable units');
+		expect(button).to.exist;
+
+		await fireEvent.click(button);
+		//Button text changes after click, tested by checking for the new text
+		expect(getByText('Hide finished and unavailable units')).to.exist;
+		//Button text changes back after another click event
+		await fireEvent.click(button);
+		expect(getByText('Show finished and unavailable units')).to.exist;
+	});
+
+	//Correctly renders UnitCardStudent for each available unit, only initially showing available units
+	it('renders UnitCardStudent for each available unit', () => {
+		const { getByText } = render(CourseOverviewStudent, {
+			props: {
+				data: mockData[0],
+				units: mockData[0].units
+			}
+		});
+
+		//Filters out units that are unavailable or have already been reflected on, and checks rendering
+		mockData[0].units.forEach((unit) => {
+			if (
+				unit.date_available.toString() < new Date().toISOString().split('T')[0] &&
+				!mockData[0].user.reflections.some((reflection) => reflection.unit_id === unit.id)
+			) {
+				expect(getByText(unit.title)).to.exist;
+			}
+		});
 	});
 });
-- 
GitLab


From 56c215054e4b17914bd289b27fa3a5a713706deb Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Fri, 19 Apr 2024 13:21:47 +0200
Subject: [PATCH 33/59] additional unitOverview tests

also added an empty structuredReport test file
---
 .../components/StructuredReport.test.svelte   |  0
 frontend/test/components/UnitOverview.test.js | 33 +++++++++++++++++--
 2 files changed, 31 insertions(+), 2 deletions(-)
 create mode 100644 frontend/test/components/StructuredReport.test.svelte

diff --git a/frontend/test/components/StructuredReport.test.svelte b/frontend/test/components/StructuredReport.test.svelte
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index 1cc9051..a3fdd5e 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,7 +1,8 @@
 /* eslint-disable no-undef */
-import { render, screen } from '@testing-library/svelte';
+import { fireEvent, render, screen } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
 import { mockData, mockLecturerUser } from '../../__mocks__/Data';
+import { goto } from '$app/navigation';
 
 describe('UnitOverview', () => {
 	test('A student user renders the student view', () => {
@@ -29,15 +30,43 @@ describe('UnitOverview', () => {
 
 		// Text which only shows up for the lecturer
 		expect(screen.queryByText(/The name of the unit, visible to the students./i)).not.toBeNull();
+		expect(screen.queryByText(/The date the student should be able to submit their reflections on this unit./i)).not.toBeNull();
+		expect(screen.queryByText(/The questions cannot be changed./i)).not.toBeNull();
 	});
 
 	test('A future unit cannot be reflected on', () => {
 		const alteredData = mockData[0];
 		alteredData.unit.unit.date_available = '3000-04-12'
-		console.log(alteredData.unit.unit.date_available)
 		render(UnitOverview, { data: alteredData });
 			// Text which only shows up when a unit is in the future
 			expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
 	});
 
+	test('Cancelling unit update', () => {
+		const lecturerData = mockData[1];
+		lecturerData.user= mockLecturerUser,
+	
+		lecturerData.role = 'lecturer'
+		
+		lecturerData.course.users = {
+			course_id: 'TDT4100',
+			course_semester: 'fall2023',
+			role: 'lecturer'
+		}
+
+		render(UnitOverview, {  data: lecturerData });
+
+		//To see the document
+		const cancelBtn = screen.getByText(/Cancel/i);
+		expect(cancelBtn).toBeInTheDocument();
+
+		// check if clicking the cancel button will cause the user to be redirected to the course view
+		fireEvent.click(cancelBtn);
+
+		expect(goto).toHaveBeenCalledWith(
+			`/courseview/${lecturerData.course.semester}/${lecturerData.course.id}`,
+			{
+				replaceState: false}
+		);
+	});
 })
-- 
GitLab


From 0a42de31e659724777ec68147158556e6125e988 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 19 Apr 2024 13:22:58 +0200
Subject: [PATCH 34/59] test(front): remove whitespace

---
 frontend/test/components/DeleteUnitModal.test.js | 2 +-
 frontend/test/components/Navbar.test.js          | 2 +-
 frontend/test/components/UnitOverview.test.js    | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/frontend/test/components/DeleteUnitModal.test.js b/frontend/test/components/DeleteUnitModal.test.js
index 9419d40..6cdab26 100644
--- a/frontend/test/components/DeleteUnitModal.test.js
+++ b/frontend/test/components/DeleteUnitModal.test.js
@@ -34,7 +34,7 @@ describe('DeleteUnitModal', () => {
 
         //Click the cancel button to remove the modal
         const cancelBtn = screen.getByText(/Cancel/i);
-        await fireEvent.click(cancelBtn)        
+        await fireEvent.click(cancelBtn)
 		expect(modalText).not.toBeInTheDocument();
 	});
 })
diff --git a/frontend/test/components/Navbar.test.js b/frontend/test/components/Navbar.test.js
index 8c82823..d52c31d 100644
--- a/frontend/test/components/Navbar.test.js
+++ b/frontend/test/components/Navbar.test.js
@@ -8,7 +8,7 @@
 //     test('the navbar is rendered', () => {
 //         const testUser = mockUser;
 //         console.log("user is:", testUser); // Verifying the mock user data
-        
+
 //         // Render with testuser
 //         render(Navbar, { user: testUser });
 
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index 1cc9051..aadcf7f 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -7,7 +7,7 @@ describe('UnitOverview', () => {
 	test('A student user renders the student view', () => {
 		render(UnitOverview, { data: mockData[0] });
 
-			// Text which only shows up for the student 
+			// Text which only shows up for the student
 			expect(screen.queryByText(/Write your reflection for this unit. Make sure not to include any sensitive or private information./i)).not.toBeNull();
 	});
 	test('A lecturer user renders the lecturer view', () => {
@@ -16,7 +16,7 @@ describe('UnitOverview', () => {
 			course: mockData[1].course,
 			unit: {
 				unit: mockData[1]
-			}, 
+			},
 			role: 'lecturer'
 		};
 		lecturerData.course.users = {
-- 
GitLab


From 3f2765755411c6ed4da84964e63783ff561145c0 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 19 Apr 2024 13:24:24 +0200
Subject: [PATCH 35/59] test(front): tests for unitcard lecturer and student

---
 .../test/components/UnitCardLecturer.test.js  | 21 +++++++++++++
 .../test/components/UnitCardStudent.test.js   | 30 +++++++++++++++++++
 2 files changed, 51 insertions(+)
 create mode 100644 frontend/test/components/UnitCardLecturer.test.js
 create mode 100644 frontend/test/components/UnitCardStudent.test.js

diff --git a/frontend/test/components/UnitCardLecturer.test.js b/frontend/test/components/UnitCardLecturer.test.js
new file mode 100644
index 0000000..2f72d20
--- /dev/null
+++ b/frontend/test/components/UnitCardLecturer.test.js
@@ -0,0 +1,21 @@
+import { render, screen } from '@testing-library/svelte';
+import UnitCardLecturer from '../../src/lib/components/UnitCardLecturer.svelte';
+import { mockUnits } from '../../__mocks__/Data';
+
+describe('UnitCardLecturer', () => {
+  test('Test the "ready"-state of the card', () => {
+    render(UnitCardLecturer, { unitData: mockUnits[0], unitTag: 'ready' });
+
+    expect(screen.queryByText('Open unit')).not.toBeNull();
+    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+    expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
+  })
+
+  test('Test the "notAvailable"-state of the card', () => {
+    render(UnitCardLecturer, { unitData: mockUnits[0], unitTag: 'notAvailable' });
+
+    expect(screen.queryByText('Edit unit')).not.toBeNull();
+    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+    expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
+  })
+});
\ No newline at end of file
diff --git a/frontend/test/components/UnitCardStudent.test.js b/frontend/test/components/UnitCardStudent.test.js
new file mode 100644
index 0000000..d52b8fb
--- /dev/null
+++ b/frontend/test/components/UnitCardStudent.test.js
@@ -0,0 +1,30 @@
+import { render, screen } from '@testing-library/svelte';
+import UnitCardStudent from '../../src/lib/components/UnitCardStudent.svelte';
+import { mockData, mockUnits } from '../../__mocks__/Data';
+
+describe('UnitCardStudent', () => {
+  test('Test the "available"-state of the card', () => {
+    render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'available' });
+
+    expect(screen.queryByText('State Machines')).not.toBeNull();
+    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+    expect(screen.queryByText('Start reflection')).not.toBeNull();
+    expect(screen.queryByText('Decline unit')).not.toBeNull();
+  })
+
+  test('Test the "submitted"-state of the card', () => {
+    render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'submitted' });
+
+    expect(screen.queryByText('State Machines')).not.toBeNull();
+    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+    expect(screen.queryByText('Reflection submitted')).not.toBeNull();
+  })
+
+  test('Test the "declined"-state of the card', () => {
+    render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'declined' });
+
+    expect(screen.queryByText('State Machines')).not.toBeNull();
+    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+    expect(screen.queryByText('Declined')).not.toBeNull();
+  })
+});
\ No newline at end of file
-- 
GitLab


From b0aa203389fafcee4cfdf0cddc8c185a6d3d7853 Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Fri, 19 Apr 2024 13:26:05 +0200
Subject: [PATCH 36/59] rename structuredReport

---
 .../{StructuredReport.test.svelte => StructuredReport.test.js}    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename frontend/test/components/{StructuredReport.test.svelte => StructuredReport.test.js} (100%)

diff --git a/frontend/test/components/StructuredReport.test.svelte b/frontend/test/components/StructuredReport.test.js
similarity index 100%
rename from frontend/test/components/StructuredReport.test.svelte
rename to frontend/test/components/StructuredReport.test.js
-- 
GitLab


From 26247d29d077bc7a93d01551c4010a21cb160056 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Fri, 19 Apr 2024 13:42:50 +0200
Subject: [PATCH 37/59] Implemented component test for CourseActions component

---
 frontend/__mocks__/Data.js                    | 22 ++++----
 .../test/components/CourseActions.test.js     | 39 ++++++++++++++
 .../components/CourseOverviewStudent.test.js  | 42 +++++++--------
 frontend/test/components/Navbar.test.js       | 20 --------
 frontend/test/components/UnitOverview.test.js | 51 +++++--------------
 5 files changed, 85 insertions(+), 89 deletions(-)
 create mode 100644 frontend/test/components/CourseActions.test.js
 delete mode 100644 frontend/test/components/Navbar.test.js

diff --git a/frontend/__mocks__/Data.js b/frontend/__mocks__/Data.js
index 0e684fe..a0b0f47 100644
--- a/frontend/__mocks__/Data.js
+++ b/frontend/__mocks__/Data.js
@@ -87,7 +87,7 @@ const mockUser = {
 	],
 	reflections: [],
 	admin: true,
-	detail: "test"
+	detail: 'test'
 };
 
 const mockLecturerUser = {
@@ -115,19 +115,21 @@ const mockLecturerUser = {
 			course_semester: 'spring2024',
 			role: 'lecturer',
 			uid: 'test',
-			missingUnits: [	{
-				id: 1,
-				date: '2025-08-23'
-			},
-			{
-				id: 2,
-				date: '2025-08-30'
-			}]
+			missingUnits: [
+				{
+					id: 1,
+					date: '2025-08-23'
+				},
+				{
+					id: 2,
+					date: '2025-08-30'
+				}
+			]
 		}
 	],
 	reflections: [],
 	admin: true,
-	detail: "test"
+	detail: 'test'
 };
 
 const mockCourse = {
diff --git a/frontend/test/components/CourseActions.test.js b/frontend/test/components/CourseActions.test.js
new file mode 100644
index 0000000..de2eae3
--- /dev/null
+++ b/frontend/test/components/CourseActions.test.js
@@ -0,0 +1,39 @@
+/* eslint-disable no-undef */
+import CourseActions from '../../src/lib/components/CourseActions.svelte';
+import { render, fireEvent } from '@testing-library/svelte';
+import { mockData } from '../../__mocks__/Data';
+
+describe('CourseActions component', () => {
+	const courseData = mockData[0].course;
+
+	//Start tests with role as student
+	let data = {
+		course: {
+			courseData
+		},
+		role: 'student'
+	};
+
+	//Correctly render unroll course button only for students
+	it('renders correct button text based on role', () => {
+		const { getByText } = render(CourseActions, { data });
+		expect(getByText('Unroll course')).toBeInTheDocument();
+	});
+
+	//Correctly render invite users button only for lecturers
+	it('renders Invite users button for lecturer role', () => {
+		//Set role to lecturer
+		data.role = 'lecturer';
+		const { getByText } = render(CourseActions, { data });
+		expect(getByText('Invite users')).toBeInTheDocument();
+	});
+
+	//Correctly render delete course button only for lecturers
+	it('renders correct modal title for lecturer role', async () => {
+		//Set role to lecturer
+		data.role = 'lecturer';
+		const { getAllByText } = render(CourseActions, { data });
+		await fireEvent.click(getAllByText('Delete course')[0]);
+		expect(getAllByText('Delete course')[0]).toBeInTheDocument();
+	});
+});
diff --git a/frontend/test/components/CourseOverviewStudent.test.js b/frontend/test/components/CourseOverviewStudent.test.js
index 4bb6cd1..146189c 100644
--- a/frontend/test/components/CourseOverviewStudent.test.js
+++ b/frontend/test/components/CourseOverviewStudent.test.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-undef */
-import { render, fireEvent } from '@testing-library/svelte';
 import CourseOverviewStudent from '../../src/lib/components/CourseOverviewStudent.svelte';
+import { render, fireEvent } from '@testing-library/svelte';
 import { mockData } from '../../__mocks__/Data';
 
 describe('CourseOverviewStudent component', () => {
@@ -16,26 +16,6 @@ describe('CourseOverviewStudent component', () => {
 		expect(getByText('No units available yet for this course')).to.exist;
 	});
 
-	//Correctly renders show/hide button, and updates accordingly based on click event
-	it('renders "Show finished and unavailable units" button and fires click event', async () => {
-		const { getByText } = render(CourseOverviewStudent, {
-			props: {
-				data: mockData[0],
-				units: mockData[0].units
-			}
-		});
-
-		const button = getByText('Show finished and unavailable units');
-		expect(button).to.exist;
-
-		await fireEvent.click(button);
-		//Button text changes after click, tested by checking for the new text
-		expect(getByText('Hide finished and unavailable units')).to.exist;
-		//Button text changes back after another click event
-		await fireEvent.click(button);
-		expect(getByText('Show finished and unavailable units')).to.exist;
-	});
-
 	//Correctly renders UnitCardStudent for each available unit, only initially showing available units
 	it('renders UnitCardStudent for each available unit', () => {
 		const { getByText } = render(CourseOverviewStudent, {
@@ -55,4 +35,24 @@ describe('CourseOverviewStudent component', () => {
 			}
 		});
 	});
+
+	//Correctly renders show/hide button, and updates accordingly based on click event
+	it('renders "Show finished and unavailable units" button and fires click event', async () => {
+		const { getByText } = render(CourseOverviewStudent, {
+			props: {
+				data: mockData[0],
+				units: mockData[0].units
+			}
+		});
+
+		const button = getByText('Show finished and unavailable units');
+		expect(button).to.exist;
+
+		await fireEvent.click(button);
+		//Button text changes after click, tested by checking for the new text
+		expect(getByText('Hide finished and unavailable units')).to.exist;
+		//Button text changes back after another click event
+		await fireEvent.click(button);
+		expect(getByText('Show finished and unavailable units')).to.exist;
+	});
 });
diff --git a/frontend/test/components/Navbar.test.js b/frontend/test/components/Navbar.test.js
deleted file mode 100644
index d52c31d..0000000
--- a/frontend/test/components/Navbar.test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// /* eslint-disable no-undef */
-
-// import { fireEvent, render, screen } from '@testing-library/svelte';
-// import Navbar from '../../src/lib/components/Navbar.svelte';
-// import { mockUser } from '../../__mocks__/Data';
-
-// describe('Navbar', () => {
-//     test('the navbar is rendered', () => {
-//         const testUser = mockUser;
-//         console.log("user is:", testUser); // Verifying the mock user data
-
-//         // Render with testuser
-//         render(Navbar, { user: testUser });
-
-//         //TODO figure out the error TypeError: window.matchMedia is not a function, as this crashing the test
-
-
-//         expect(screen.getByText(/Logged in as/i)).toBeInTheDocument();
-//     });
-// });
\ No newline at end of file
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index 73e357d..349cf59 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -8,8 +8,12 @@ describe('UnitOverview', () => {
 	test('A student user renders the student view', () => {
 		render(UnitOverview, { data: mockData[0] });
 
-			// Text which only shows up for the student
-			expect(screen.queryByText(/Write your reflection for this unit. Make sure not to include any sensitive or private information./i)).not.toBeNull();
+		// Text which only shows up for the student
+		expect(
+			screen.queryByText(
+				/Write your reflection for this unit. Make sure not to include any sensitive or private information./i
+			)
+		).not.toBeNull();
 	});
 	test('A lecturer user renders the lecturer view', () => {
 		const lecturerData = {
@@ -18,55 +22,26 @@ describe('UnitOverview', () => {
 			unit: {
 				unit: mockData[1]
 			},
+			},
 			role: 'lecturer'
 		};
 		lecturerData.course.users = {
 			course_id: 'TDT4100',
 			course_semester: 'fall2023',
 			role: 'lecturer'
-		}
+		};
 
-		render(UnitOverview, {  data: lecturerData });
+		render(UnitOverview, { data: lecturerData });
 
 		// Text which only shows up for the lecturer
 		expect(screen.queryByText(/The name of the unit, visible to the students./i)).not.toBeNull();
-		expect(screen.queryByText(/The date the student should be able to submit their reflections on this unit./i)).not.toBeNull();
-		expect(screen.queryByText(/The questions cannot be changed./i)).not.toBeNull();
 	});
 
 	test('A future unit cannot be reflected on', () => {
 		const alteredData = mockData[0];
-		alteredData.unit.unit.date_available = '3000-04-12'
+		alteredData.unit.unit.date_available = '3000-04-12';
 		render(UnitOverview, { data: alteredData });
-			// Text which only shows up when a unit is in the future
-			expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
-	});
-
-	test('Cancelling unit update', () => {
-		const lecturerData = mockData[1];
-		lecturerData.user= mockLecturerUser,
-	
-		lecturerData.role = 'lecturer'
-		
-		lecturerData.course.users = {
-			course_id: 'TDT4100',
-			course_semester: 'fall2023',
-			role: 'lecturer'
-		}
-
-		render(UnitOverview, {  data: lecturerData });
-
-		//To see the document
-		const cancelBtn = screen.getByText(/Cancel/i);
-		expect(cancelBtn).toBeInTheDocument();
-
-		// check if clicking the cancel button will cause the user to be redirected to the course view
-		fireEvent.click(cancelBtn);
-
-		expect(goto).toHaveBeenCalledWith(
-			`/courseview/${lecturerData.course.semester}/${lecturerData.course.id}`,
-			{
-				replaceState: false}
-		);
+		// Text which only shows up when a unit is in the future
+		expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
 	});
-})
+});
-- 
GitLab


From d2dc779a930ab768fcb2449e7f6a97912da5b02c Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Fri, 19 Apr 2024 13:44:57 +0200
Subject: [PATCH 38/59] Fix testing

---
 .gitignore                                    |   1 +
 backend/api/main.py                           |   5 +
 backend/api/schemas.py                        |   1 +
 frontend/package-lock.json                    | 246 ++++++------------
 frontend/package.json                         |   2 +
 .../src/lib/components/CourseCards.svelte     |  34 +--
 frontend/src/types.d.ts                       |   1 +
 7 files changed, 86 insertions(+), 204 deletions(-)

diff --git a/.gitignore b/.gitignore
index c7feb33..a840009 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,6 +115,7 @@ env.bak/
 venv.bak/
 .nvmrc
 !.env.template
+coverage/
 
 # Spyder project settings
 .spyderproject
diff --git a/backend/api/main.py b/backend/api/main.py
index e10945a..84ce862 100644
--- a/backend/api/main.py
+++ b/backend/api/main.py
@@ -392,6 +392,11 @@ async def user(request: Request, db: Session = Depends(get_db)):
                 if unit["id"] not in reflected_units
             ]
 
+            course = crud.get_course(
+                db, enrollment.course_id, enrollment.course_semester
+            )
+            enrollment.course_name = course.name
+
     if user == None:
         request.session.pop("user")
         raise HTTPException(404, detail="User not found")
diff --git a/backend/api/schemas.py b/backend/api/schemas.py
index f6a0a08..23a375e 100644
--- a/backend/api/schemas.py
+++ b/backend/api/schemas.py
@@ -137,6 +137,7 @@ class EnrollUser(EnrollmentBase):
 class Enrollment(EnrollmentBase):
     uid: str
     missingUnits: Any = []
+    course_name: str = ""
 
 
 class UserAdmin(BaseModel):
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 9182ae8..8e36886 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,6 +11,7 @@
 				"@popperjs/core": "^2.11.8",
 				"@sveltejs/adapter-vercel": "^5.1.0",
 				"@testing-library/user-event": "^14.5.2",
+				"@vitest/coverage-v8": "^1.5.0",
 				"classnames": "^2.5.1",
 				"date-picker-svelte": "^2.11.0",
 				"dotenv": "^16.4.5",
@@ -295,7 +296,6 @@
 			"version": "7.23.4",
 			"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
 			"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=6.9.0"
 			}
@@ -349,10 +349,9 @@
 			}
 		},
 		"node_modules/@babel/parser": {
-			"version": "7.24.0",
-			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
-			"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
-			"dev": true,
+			"version": "7.24.4",
+			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
+			"integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
 			"bin": {
 				"parser": "bin/babel-parser.js"
 			},
@@ -619,7 +618,6 @@
 			"version": "7.24.0",
 			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
 			"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
-			"dev": true,
 			"dependencies": {
 				"@babel/helper-string-parser": "^7.23.4",
 				"@babel/helper-validator-identifier": "^7.22.20",
@@ -632,10 +630,7 @@
 		"node_modules/@bcoe/v8-coverage": {
 			"version": "0.2.3",
 			"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
-			"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
-			"dev": true,
-			"optional": true,
-			"peer": true
+			"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
 		},
 		"node_modules/@colors/colors": {
 			"version": "1.5.0",
@@ -1328,9 +1323,6 @@
 			"version": "0.1.3",
 			"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
 			"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -1816,7 +1808,6 @@
 			"version": "29.6.3",
 			"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
 			"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
-			"dev": true,
 			"dependencies": {
 				"@sinclair/typebox": "^0.27.8"
 			},
@@ -2439,8 +2430,7 @@
 		"node_modules/@sinclair/typebox": {
 			"version": "0.27.8",
 			"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
-			"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
-			"dev": true
+			"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
 		},
 		"node_modules/@sinonjs/commons": {
 			"version": "3.0.1",
@@ -2783,7 +2773,6 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
 			"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -3357,11 +3346,49 @@
 				"node": ">=16"
 			}
 		},
+		"node_modules/@vitest/coverage-v8": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz",
+			"integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==",
+			"dependencies": {
+				"@ampproject/remapping": "^2.2.1",
+				"@bcoe/v8-coverage": "^0.2.3",
+				"debug": "^4.3.4",
+				"istanbul-lib-coverage": "^3.2.2",
+				"istanbul-lib-report": "^3.0.1",
+				"istanbul-lib-source-maps": "^5.0.4",
+				"istanbul-reports": "^3.1.6",
+				"magic-string": "^0.30.5",
+				"magicast": "^0.3.3",
+				"picocolors": "^1.0.0",
+				"std-env": "^3.5.0",
+				"strip-literal": "^2.0.0",
+				"test-exclude": "^6.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/vitest"
+			},
+			"peerDependencies": {
+				"vitest": "1.5.0"
+			}
+		},
+		"node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": {
+			"version": "5.0.4",
+			"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz",
+			"integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==",
+			"dependencies": {
+				"@jridgewell/trace-mapping": "^0.3.23",
+				"debug": "^4.1.1",
+				"istanbul-lib-coverage": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			}
+		},
 		"node_modules/@vitest/expect": {
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz",
 			"integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==",
-			"dev": true,
 			"dependencies": {
 				"@vitest/spy": "1.5.0",
 				"@vitest/utils": "1.5.0",
@@ -3375,7 +3402,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz",
 			"integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==",
-			"dev": true,
 			"dependencies": {
 				"@vitest/utils": "1.5.0",
 				"p-limit": "^5.0.0",
@@ -3389,7 +3415,6 @@
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
 			"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
-			"dev": true,
 			"dependencies": {
 				"yocto-queue": "^1.0.0"
 			},
@@ -3404,7 +3429,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
 			"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
-			"dev": true,
 			"engines": {
 				"node": ">=12.20"
 			},
@@ -3416,7 +3440,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz",
 			"integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==",
-			"dev": true,
 			"dependencies": {
 				"magic-string": "^0.30.5",
 				"pathe": "^1.1.1",
@@ -3430,7 +3453,6 @@
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
 			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-			"dev": true,
 			"engines": {
 				"node": ">=10"
 			},
@@ -3442,7 +3464,6 @@
 			"version": "29.7.0",
 			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
 			"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
-			"dev": true,
 			"dependencies": {
 				"@jest/schemas": "^29.6.3",
 				"ansi-styles": "^5.0.0",
@@ -3455,14 +3476,12 @@
 		"node_modules/@vitest/snapshot/node_modules/react-is": {
 			"version": "18.2.0",
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
-			"dev": true
+			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 		},
 		"node_modules/@vitest/spy": {
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz",
 			"integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==",
-			"dev": true,
 			"dependencies": {
 				"tinyspy": "^2.2.0"
 			},
@@ -3474,7 +3493,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz",
 			"integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==",
-			"dev": true,
 			"dependencies": {
 				"diff-sequences": "^29.6.3",
 				"estree-walker": "^3.0.3",
@@ -3489,7 +3507,6 @@
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
 			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-			"dev": true,
 			"engines": {
 				"node": ">=10"
 			},
@@ -3501,7 +3518,6 @@
 			"version": "3.0.3",
 			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
 			"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
-			"dev": true,
 			"dependencies": {
 				"@types/estree": "^1.0.0"
 			}
@@ -3510,7 +3526,6 @@
 			"version": "29.7.0",
 			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
 			"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
-			"dev": true,
 			"dependencies": {
 				"@jest/schemas": "^29.6.3",
 				"ansi-styles": "^5.0.0",
@@ -3523,8 +3538,7 @@
 		"node_modules/@vitest/utils/node_modules/react-is": {
 			"version": "18.2.0",
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
-			"dev": true
+			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 		},
 		"node_modules/@yr/monotone-cubic-spline": {
 			"version": "1.0.3",
@@ -3536,7 +3550,6 @@
 			"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
 			"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
 			"deprecated": "Use your platform's native atob() and btoa() methods instead",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -3560,7 +3573,6 @@
 			"version": "7.0.1",
 			"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
 			"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -3589,7 +3601,6 @@
 			"version": "8.3.2",
 			"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
 			"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
-			"dev": true,
 			"engines": {
 				"node": ">=0.4.0"
 			}
@@ -3769,7 +3780,6 @@
 			"version": "1.1.0",
 			"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
 			"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
-			"dev": true,
 			"engines": {
 				"node": "*"
 			}
@@ -3783,7 +3793,6 @@
 			"version": "0.4.0",
 			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
 			"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4241,7 +4250,6 @@
 			"version": "6.7.14",
 			"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
 			"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -4316,7 +4324,6 @@
 			"version": "4.4.1",
 			"resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
 			"integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
-			"dev": true,
 			"dependencies": {
 				"assertion-error": "^1.1.0",
 				"check-error": "^1.0.3",
@@ -4367,7 +4374,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
 			"integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
-			"dev": true,
 			"dependencies": {
 				"get-func-name": "^2.0.2"
 			},
@@ -4556,7 +4562,6 @@
 			"version": "1.0.8",
 			"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
 			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -4787,7 +4792,6 @@
 			"version": "0.5.0",
 			"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
 			"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4795,7 +4799,6 @@
 			"version": "2.3.0",
 			"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
 			"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -4809,7 +4812,6 @@
 			"version": "0.3.8",
 			"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
 			"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4817,7 +4819,6 @@
 			"version": "3.0.2",
 			"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
 			"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -4857,7 +4858,6 @@
 			"version": "10.4.3",
 			"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
 			"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4881,7 +4881,6 @@
 			"version": "4.1.3",
 			"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
 			"integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
-			"dev": true,
 			"dependencies": {
 				"type-detect": "^4.0.0"
 			},
@@ -4970,7 +4969,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
 			"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -5032,7 +5030,6 @@
 			"version": "29.6.3",
 			"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
 			"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
-			"dev": true,
 			"engines": {
 				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
 			}
@@ -5082,7 +5079,6 @@
 			"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
 			"integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
 			"deprecated": "Use your platform's native DOMException instead",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -5146,7 +5142,6 @@
 			"version": "4.5.0",
 			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
 			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -5267,7 +5262,6 @@
 			"version": "2.1.0",
 			"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
 			"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -5602,7 +5596,6 @@
 			"version": "4.0.1",
 			"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
 			"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"bin": {
@@ -5641,7 +5634,7 @@
 			"version": "5.3.0",
 			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
 			"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-			"dev": true,
+			"devOptional": true,
 			"engines": {
 				"node": ">=4.0"
 			}
@@ -5655,7 +5648,7 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
 			"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-			"dev": true,
+			"devOptional": true,
 			"engines": {
 				"node": ">=0.10.0"
 			}
@@ -5670,7 +5663,6 @@
 			"version": "8.0.1",
 			"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
 			"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
-			"dev": true,
 			"dependencies": {
 				"cross-spawn": "^7.0.3",
 				"get-stream": "^8.0.1",
@@ -5693,7 +5685,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
 			"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
-			"dev": true,
 			"engines": {
 				"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 			},
@@ -5705,7 +5696,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
 			"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
-			"dev": true,
 			"engines": {
 				"node": ">=12"
 			},
@@ -5717,7 +5707,6 @@
 			"version": "5.3.0",
 			"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
 			"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
-			"dev": true,
 			"dependencies": {
 				"path-key": "^4.0.0"
 			},
@@ -5732,7 +5721,6 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
 			"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
-			"dev": true,
 			"dependencies": {
 				"mimic-fn": "^4.0.0"
 			},
@@ -5747,7 +5735,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
 			"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=12"
 			},
@@ -5759,7 +5746,6 @@
 			"version": "4.1.0",
 			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
 			"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
-			"dev": true,
 			"engines": {
 				"node": ">=14"
 			},
@@ -5771,7 +5757,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
 			"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
-			"dev": true,
 			"engines": {
 				"node": ">=12"
 			},
@@ -6021,7 +6006,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
 			"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6167,7 +6151,6 @@
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
 			"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
-			"dev": true,
 			"engines": {
 				"node": "*"
 			}
@@ -6205,7 +6188,6 @@
 			"version": "8.0.1",
 			"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
 			"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
-			"dev": true,
 			"engines": {
 				"node": ">=16"
 			},
@@ -6418,7 +6400,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
 			"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6431,16 +6412,12 @@
 		"node_modules/html-escaper": {
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
-			"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
-			"dev": true,
-			"optional": true,
-			"peer": true
+			"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="
 		},
 		"node_modules/http-proxy-agent": {
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
 			"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6468,7 +6445,6 @@
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
 			"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=16.17.0"
 			}
@@ -6477,7 +6453,6 @@
 			"version": "0.6.3",
 			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 			"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6811,7 +6786,6 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
 			"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -6949,9 +6923,6 @@
 			"version": "3.2.2",
 			"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
 			"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -7017,9 +6988,6 @@
 			"version": "3.0.1",
 			"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
 			"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"istanbul-lib-coverage": "^3.0.0",
 				"make-dir": "^4.0.0",
@@ -7033,9 +7001,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -7044,9 +7009,6 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
 			"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"yallist": "^4.0.0"
 			},
@@ -7058,9 +7020,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
 			"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"semver": "^7.5.3"
 			},
@@ -7075,9 +7034,6 @@
 			"version": "7.6.0",
 			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
 			"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"lru-cache": "^6.0.0"
 			},
@@ -7092,9 +7048,6 @@
 			"version": "7.2.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"has-flag": "^4.0.0"
 			},
@@ -7105,10 +7058,7 @@
 		"node_modules/istanbul-lib-report/node_modules/yallist": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-			"dev": true,
-			"optional": true,
-			"peer": true
+			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
 		},
 		"node_modules/istanbul-lib-source-maps": {
 			"version": "4.0.1",
@@ -7130,9 +7080,6 @@
 			"version": "3.1.7",
 			"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
 			"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"html-escaper": "^2.0.0",
 				"istanbul-lib-report": "^3.0.0"
@@ -9340,7 +9287,6 @@
 			"version": "20.0.3",
 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
 			"integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -9436,8 +9382,7 @@
 		"node_modules/jsonc-parser": {
 			"version": "3.2.1",
 			"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
-			"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
-			"dev": true
+			"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA=="
 		},
 		"node_modules/jsonfile": {
 			"version": "6.1.0",
@@ -9519,7 +9464,6 @@
 			"version": "0.5.0",
 			"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
 			"integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
-			"dev": true,
 			"dependencies": {
 				"mlly": "^1.4.2",
 				"pkg-types": "^1.0.3"
@@ -9566,7 +9510,6 @@
 			"version": "2.3.7",
 			"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
 			"integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
-			"dev": true,
 			"dependencies": {
 				"get-func-name": "^2.0.1"
 			}
@@ -9601,6 +9544,16 @@
 				"node": ">=12"
 			}
 		},
+		"node_modules/magicast": {
+			"version": "0.3.4",
+			"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz",
+			"integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==",
+			"dependencies": {
+				"@babel/parser": "^7.24.4",
+				"@babel/types": "^7.24.0",
+				"source-map-js": "^1.2.0"
+			}
+		},
 		"node_modules/make-dir": {
 			"version": "3.1.0",
 			"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -9647,8 +9600,7 @@
 		"node_modules/merge-stream": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
-			"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
-			"dev": true
+			"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
 		},
 		"node_modules/merge2": {
 			"version": "1.4.1",
@@ -9674,7 +9626,6 @@
 			"version": "1.52.0",
 			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
 			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -9685,7 +9636,6 @@
 			"version": "2.1.35",
 			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
 			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -9796,7 +9746,6 @@
 			"version": "1.6.1",
 			"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
 			"integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==",
-			"dev": true,
 			"dependencies": {
 				"acorn": "^8.11.3",
 				"pathe": "^1.1.2",
@@ -9978,7 +9927,6 @@
 			"version": "2.2.7",
 			"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
 			"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -10177,7 +10125,6 @@
 			"version": "7.1.2",
 			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
 			"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -10258,14 +10205,12 @@
 		"node_modules/pathe": {
 			"version": "1.1.2",
 			"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
-			"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
-			"dev": true
+			"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
 		},
 		"node_modules/pathval": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
 			"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
-			"dev": true,
 			"engines": {
 				"node": "*"
 			}
@@ -10338,7 +10283,6 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
 			"integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
-			"dev": true,
 			"dependencies": {
 				"jsonc-parser": "^3.2.0",
 				"mlly": "^1.2.0",
@@ -10664,7 +10608,6 @@
 			"version": "1.9.0",
 			"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
 			"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -10806,7 +10749,7 @@
 			"version": "2.3.1",
 			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 			"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
-			"dev": true,
+			"devOptional": true,
 			"engines": {
 				"node": ">=6"
 			}
@@ -10833,7 +10776,6 @@
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
 			"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -10958,7 +10900,6 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
 			"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -11121,7 +11062,6 @@
 			"version": "2.1.2",
 			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -11153,7 +11093,6 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
 			"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -11250,8 +11189,7 @@
 		"node_modules/siginfo": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
-			"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
-			"dev": true
+			"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="
 		},
 		"node_modules/signal-exit": {
 			"version": "3.0.7",
@@ -11307,7 +11245,6 @@
 			"version": "0.6.1",
 			"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -11315,9 +11252,9 @@
 			}
 		},
 		"node_modules/source-map-js": {
-			"version": "1.0.2",
-			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
-			"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+			"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
 			"engines": {
 				"node": ">=0.10.0"
 			}
@@ -11372,14 +11309,12 @@
 		"node_modules/stackback": {
 			"version": "0.0.2",
 			"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
-			"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
-			"dev": true
+			"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="
 		},
 		"node_modules/std-env": {
 			"version": "3.7.0",
 			"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
-			"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
-			"dev": true
+			"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg=="
 		},
 		"node_modules/stop-iteration-iterator": {
 			"version": "1.0.0",
@@ -11513,7 +11448,6 @@
 			"version": "2.1.0",
 			"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz",
 			"integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==",
-			"dev": true,
 			"dependencies": {
 				"js-tokens": "^9.0.0"
 			},
@@ -11524,8 +11458,7 @@
 		"node_modules/strip-literal/node_modules/js-tokens": {
 			"version": "9.0.0",
 			"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz",
-			"integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==",
-			"dev": true
+			"integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ=="
 		},
 		"node_modules/sucrase": {
 			"version": "3.35.0",
@@ -11852,7 +11785,6 @@
 			"version": "3.2.4",
 			"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
 			"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -11940,9 +11872,6 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
 			"integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"@istanbuljs/schema": "^0.1.2",
 				"glob": "^7.1.4",
@@ -11956,9 +11885,6 @@
 			"version": "1.1.11",
 			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
 			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0",
 				"concat-map": "0.0.1"
@@ -11968,9 +11894,6 @@
 			"version": "3.1.2",
 			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
 			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-			"dev": true,
-			"optional": true,
-			"peer": true,
 			"dependencies": {
 				"brace-expansion": "^1.1.7"
 			},
@@ -12015,14 +11938,12 @@
 		"node_modules/tinybench": {
 			"version": "2.7.0",
 			"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz",
-			"integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==",
-			"dev": true
+			"integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA=="
 		},
 		"node_modules/tinypool": {
 			"version": "0.8.4",
 			"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
 			"integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
-			"dev": true,
 			"engines": {
 				"node": ">=14.0.0"
 			}
@@ -12031,7 +11952,6 @@
 			"version": "2.2.1",
 			"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
 			"integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
-			"dev": true,
 			"engines": {
 				"node": ">=14.0.0"
 			}
@@ -12048,7 +11968,6 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
 			"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
-			"dev": true,
 			"engines": {
 				"node": ">=4"
 			}
@@ -12082,7 +12001,6 @@
 			"version": "4.1.3",
 			"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
 			"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12099,7 +12017,6 @@
 			"version": "0.2.0",
 			"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
 			"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12110,7 +12027,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
 			"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12204,7 +12120,6 @@
 			"version": "4.0.8",
 			"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
 			"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
-			"dev": true,
 			"engines": {
 				"node": ">=4"
 			}
@@ -12239,8 +12154,7 @@
 		"node_modules/ufo": {
 			"version": "1.5.3",
 			"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
-			"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
-			"dev": true
+			"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw=="
 		},
 		"node_modules/undici-types": {
 			"version": "5.26.5",
@@ -12300,7 +12214,6 @@
 			"version": "1.5.10",
 			"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
 			"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12387,7 +12300,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz",
 			"integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==",
-			"dev": true,
 			"dependencies": {
 				"cac": "^6.7.14",
 				"debug": "^4.3.4",
@@ -12422,7 +12334,6 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz",
 			"integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==",
-			"dev": true,
 			"dependencies": {
 				"@vitest/expect": "1.5.0",
 				"@vitest/runner": "1.5.0",
@@ -12496,7 +12407,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
 			"integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12521,7 +12431,6 @@
 			"version": "7.0.0",
 			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
 			"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12532,7 +12441,6 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
 			"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12546,7 +12454,6 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
 			"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12557,7 +12464,6 @@
 			"version": "11.0.0",
 			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
 			"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12636,7 +12542,6 @@
 			"version": "2.2.2",
 			"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
 			"integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
-			"dev": true,
 			"dependencies": {
 				"siginfo": "^2.0.0",
 				"stackback": "0.0.2"
@@ -12800,7 +12705,6 @@
 			"version": "8.16.0",
 			"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
 			"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12823,7 +12727,6 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
 			"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
-			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12834,7 +12737,6 @@
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
 			"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
-			"dev": true,
 			"optional": true,
 			"peer": true
 		},
diff --git a/frontend/package.json b/frontend/package.json
index 43f8feb..5cb7fd4 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,6 +4,7 @@
 	"private": true,
 	"scripts": {
 		"test": "vitest",
+		"test:coverage": "vitest --coverage",
 		"dev": "vite dev --host 127.0.0.1",
 		"build": "vite build",
 		"preview": "vite preview",
@@ -45,6 +46,7 @@
 		"@popperjs/core": "^2.11.8",
 		"@sveltejs/adapter-vercel": "^5.1.0",
 		"@testing-library/user-event": "^14.5.2",
+		"@vitest/coverage-v8": "^1.5.0",
 		"classnames": "^2.5.1",
 		"date-picker-svelte": "^2.11.0",
 		"dotenv": "^16.4.5",
diff --git a/frontend/src/lib/components/CourseCards.svelte b/frontend/src/lib/components/CourseCards.svelte
index c4f04ba..6dc6fdb 100644
--- a/frontend/src/lib/components/CourseCards.svelte
+++ b/frontend/src/lib/components/CourseCards.svelte
@@ -5,36 +5,6 @@
 	import { Card, Button, Badge } from 'flowbite-svelte';
 	import { PUBLIC_API_URL } from '$env/static/public';
 
-	let coursesWithNames: (Enrollment & { name: string })[] = [];
-
-	//Update courseWithNames when a new course is created
-	$: {
-		(async () => {
-			coursesWithNames = await Promise.all(
-				courses.map(async (course) => {
-					const name = await getCourseName(course.course_id, course.course_semester);
-					return { ...course, name };
-				})
-			);
-		})();
-	}
-
-	//Fetch to get course names
-	async function getCourseName(courseId: string, courseSemester: string): Promise<string> {
-		const response = await fetch(
-			`${PUBLIC_API_URL}/course?course_id=${courseId}&course_semester=${courseSemester}`,
-			{
-				method: 'GET',
-				credentials: 'include',
-				headers: {
-					'Content-Type': 'application/json'
-				}
-			}
-		);
-		const res = await response.json();
-		return res.name;
-	}
-
 	const formatSemester = (semester: string): string => {
 		const season = semester.slice(0, -4);
 		const year = semester.slice(-4);
@@ -42,7 +12,7 @@
 	};
 </script>
 
-{#each coursesWithNames as course}
+{#each courses as course}
 	<Card
 		on:click={() => goto(`/courseview/${course.course_semester}/${course.course_id}`)}
 		class="m-auto cursor-pointer hover:bg-teal-2 dark:bg-gray-800 dark:hover:bg-gray-700"
@@ -51,7 +21,7 @@
 			style="font-size: 1.25rem;"
 			class="mb-1 select-none text-4xl font-bold tracking-tight text-gray-900 dark:text-white text-ellipsis overflow-hidden whitespace-nowrap"
 		>
-			{course.course_id} - {course.name}
+			{course.course_id} - {course.course_name}
 		</h5>
 		<p class="mb-3 select-none font-normal text-gray-700 dark:text-gray-300 leading-tight">
 			{formatSemester(course.course_semester)}
diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts
index 59f7595..a2aa266 100644
--- a/frontend/src/types.d.ts
+++ b/frontend/src/types.d.ts
@@ -30,6 +30,7 @@ type Enrollment = {
 	course_id: string;
 	course_semester: string;
 	role: string;
+	course_name: string;
 };
 
 type Course = {
-- 
GitLab


From 02313ce61e3dd92dafdcd476e6a4f1b9a81d8814 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Fri, 19 Apr 2024 13:41:03 +0200
Subject: [PATCH 39/59] Implemented component test for CourseActions component

---
 frontend/test/components/UnitOverview.test.js | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index 349cf59..bc95dc3 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -14,6 +14,12 @@ describe('UnitOverview', () => {
 				/Write your reflection for this unit. Make sure not to include any sensitive or private information./i
 			)
 		).not.toBeNull();
+		// Text which only shows up for the student
+		expect(
+			screen.queryByText(
+				/Write your reflection for this unit. Make sure not to include any sensitive or private information./i
+			)
+		).not.toBeNull();
 	});
 	test('A lecturer user renders the lecturer view', () => {
 		const lecturerData = {
@@ -22,7 +28,6 @@ describe('UnitOverview', () => {
 			unit: {
 				unit: mockData[1]
 			},
-			},
 			role: 'lecturer'
 		};
 		lecturerData.course.users = {
@@ -30,7 +35,9 @@ describe('UnitOverview', () => {
 			course_semester: 'fall2023',
 			role: 'lecturer'
 		};
+		};
 
+		render(UnitOverview, { data: lecturerData });
 		render(UnitOverview, { data: lecturerData });
 
 		// Text which only shows up for the lecturer
@@ -40,8 +47,12 @@ describe('UnitOverview', () => {
 	test('A future unit cannot be reflected on', () => {
 		const alteredData = mockData[0];
 		alteredData.unit.unit.date_available = '3000-04-12';
+		alteredData.unit.unit.date_available = '3000-04-12';
 		render(UnitOverview, { data: alteredData });
 		// Text which only shows up when a unit is in the future
 		expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
+		// Text which only shows up when a unit is in the future
+		expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
 	});
 });
+});
-- 
GitLab


From c2a238f7cd5590673a925f802be93e8837f4a0c2 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Fri, 19 Apr 2024 13:51:53 +0200
Subject: [PATCH 40/59] Fix UnitOverview test

---
 frontend/package-lock.json                    | 159 +++++++++++++++---
 frontend/package.json                         |   2 +-
 frontend/test/components/UnitOverview.test.js |   5 +-
 3 files changed, 143 insertions(+), 23 deletions(-)

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 8e36886..7b75304 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,7 +11,6 @@
 				"@popperjs/core": "^2.11.8",
 				"@sveltejs/adapter-vercel": "^5.1.0",
 				"@testing-library/user-event": "^14.5.2",
-				"@vitest/coverage-v8": "^1.5.0",
 				"classnames": "^2.5.1",
 				"date-picker-svelte": "^2.11.0",
 				"dotenv": "^16.4.5",
@@ -32,6 +31,7 @@
 				"@types/jest": "^29.5.12",
 				"@typescript-eslint/eslint-plugin": "^7.1.1",
 				"@typescript-eslint/parser": "^7.1.1",
+				"@vitest/coverage-v8": "^1.5.0",
 				"autoprefixer": "^10.4.18",
 				"eslint": "^8.57.0",
 				"eslint-config-prettier": "^9.1.0",
@@ -296,6 +296,7 @@
 			"version": "7.23.4",
 			"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
 			"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+			"dev": true,
 			"engines": {
 				"node": ">=6.9.0"
 			}
@@ -352,6 +353,7 @@
 			"version": "7.24.4",
 			"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
 			"integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
+			"dev": true,
 			"bin": {
 				"parser": "bin/babel-parser.js"
 			},
@@ -618,6 +620,7 @@
 			"version": "7.24.0",
 			"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
 			"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
+			"dev": true,
 			"dependencies": {
 				"@babel/helper-string-parser": "^7.23.4",
 				"@babel/helper-validator-identifier": "^7.22.20",
@@ -630,7 +633,8 @@
 		"node_modules/@bcoe/v8-coverage": {
 			"version": "0.2.3",
 			"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
-			"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
+			"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+			"dev": true
 		},
 		"node_modules/@colors/colors": {
 			"version": "1.5.0",
@@ -1323,6 +1327,7 @@
 			"version": "0.1.3",
 			"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
 			"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -1808,6 +1813,7 @@
 			"version": "29.6.3",
 			"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
 			"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+			"dev": true,
 			"dependencies": {
 				"@sinclair/typebox": "^0.27.8"
 			},
@@ -2430,7 +2436,8 @@
 		"node_modules/@sinclair/typebox": {
 			"version": "0.27.8",
 			"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
-			"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
+			"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+			"dev": true
 		},
 		"node_modules/@sinonjs/commons": {
 			"version": "3.0.1",
@@ -2773,6 +2780,7 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
 			"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -3350,6 +3358,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.5.0.tgz",
 			"integrity": "sha512-1igVwlcqw1QUMdfcMlzzY4coikSIBN944pkueGi0pawrX5I5Z+9hxdTR+w3Sg6Q3eZhvdMAs8ZaF9JuTG1uYOQ==",
+			"dev": true,
 			"dependencies": {
 				"@ampproject/remapping": "^2.2.1",
 				"@bcoe/v8-coverage": "^0.2.3",
@@ -3376,6 +3385,7 @@
 			"version": "5.0.4",
 			"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz",
 			"integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==",
+			"dev": true,
 			"dependencies": {
 				"@jridgewell/trace-mapping": "^0.3.23",
 				"debug": "^4.1.1",
@@ -3389,6 +3399,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz",
 			"integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==",
+			"dev": true,
 			"dependencies": {
 				"@vitest/spy": "1.5.0",
 				"@vitest/utils": "1.5.0",
@@ -3402,6 +3413,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz",
 			"integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==",
+			"dev": true,
 			"dependencies": {
 				"@vitest/utils": "1.5.0",
 				"p-limit": "^5.0.0",
@@ -3415,6 +3427,7 @@
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
 			"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+			"dev": true,
 			"dependencies": {
 				"yocto-queue": "^1.0.0"
 			},
@@ -3429,6 +3442,7 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
 			"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+			"dev": true,
 			"engines": {
 				"node": ">=12.20"
 			},
@@ -3440,6 +3454,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz",
 			"integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==",
+			"dev": true,
 			"dependencies": {
 				"magic-string": "^0.30.5",
 				"pathe": "^1.1.1",
@@ -3453,6 +3468,7 @@
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
 			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+			"dev": true,
 			"engines": {
 				"node": ">=10"
 			},
@@ -3464,6 +3480,7 @@
 			"version": "29.7.0",
 			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
 			"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+			"dev": true,
 			"dependencies": {
 				"@jest/schemas": "^29.6.3",
 				"ansi-styles": "^5.0.0",
@@ -3476,12 +3493,14 @@
 		"node_modules/@vitest/snapshot/node_modules/react-is": {
 			"version": "18.2.0",
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+			"dev": true
 		},
 		"node_modules/@vitest/spy": {
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz",
 			"integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==",
+			"dev": true,
 			"dependencies": {
 				"tinyspy": "^2.2.0"
 			},
@@ -3493,6 +3512,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz",
 			"integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==",
+			"dev": true,
 			"dependencies": {
 				"diff-sequences": "^29.6.3",
 				"estree-walker": "^3.0.3",
@@ -3507,6 +3527,7 @@
 			"version": "5.2.0",
 			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
 			"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+			"dev": true,
 			"engines": {
 				"node": ">=10"
 			},
@@ -3518,6 +3539,7 @@
 			"version": "3.0.3",
 			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
 			"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+			"dev": true,
 			"dependencies": {
 				"@types/estree": "^1.0.0"
 			}
@@ -3526,6 +3548,7 @@
 			"version": "29.7.0",
 			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
 			"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+			"dev": true,
 			"dependencies": {
 				"@jest/schemas": "^29.6.3",
 				"ansi-styles": "^5.0.0",
@@ -3538,7 +3561,8 @@
 		"node_modules/@vitest/utils/node_modules/react-is": {
 			"version": "18.2.0",
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+			"dev": true
 		},
 		"node_modules/@yr/monotone-cubic-spline": {
 			"version": "1.0.3",
@@ -3550,6 +3574,7 @@
 			"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
 			"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
 			"deprecated": "Use your platform's native atob() and btoa() methods instead",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -3573,6 +3598,7 @@
 			"version": "7.0.1",
 			"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
 			"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -3601,6 +3627,7 @@
 			"version": "8.3.2",
 			"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
 			"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+			"dev": true,
 			"engines": {
 				"node": ">=0.4.0"
 			}
@@ -3780,6 +3807,7 @@
 			"version": "1.1.0",
 			"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
 			"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+			"dev": true,
 			"engines": {
 				"node": "*"
 			}
@@ -3793,6 +3821,7 @@
 			"version": "0.4.0",
 			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
 			"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4250,6 +4279,7 @@
 			"version": "6.7.14",
 			"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
 			"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -4324,6 +4354,7 @@
 			"version": "4.4.1",
 			"resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
 			"integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
+			"dev": true,
 			"dependencies": {
 				"assertion-error": "^1.1.0",
 				"check-error": "^1.0.3",
@@ -4374,6 +4405,7 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
 			"integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+			"dev": true,
 			"dependencies": {
 				"get-func-name": "^2.0.2"
 			},
@@ -4562,6 +4594,7 @@
 			"version": "1.0.8",
 			"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
 			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -4792,6 +4825,7 @@
 			"version": "0.5.0",
 			"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
 			"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4799,6 +4833,7 @@
 			"version": "2.3.0",
 			"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
 			"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -4812,6 +4847,7 @@
 			"version": "0.3.8",
 			"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
 			"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4819,6 +4855,7 @@
 			"version": "3.0.2",
 			"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
 			"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -4858,6 +4895,7 @@
 			"version": "10.4.3",
 			"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
 			"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -4881,6 +4919,7 @@
 			"version": "4.1.3",
 			"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
 			"integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
+			"dev": true,
 			"dependencies": {
 				"type-detect": "^4.0.0"
 			},
@@ -4969,6 +5008,7 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
 			"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -5030,6 +5070,7 @@
 			"version": "29.6.3",
 			"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
 			"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+			"dev": true,
 			"engines": {
 				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
 			}
@@ -5079,6 +5120,7 @@
 			"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
 			"integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
 			"deprecated": "Use your platform's native DOMException instead",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -5142,6 +5184,7 @@
 			"version": "4.5.0",
 			"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
 			"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -5262,6 +5305,7 @@
 			"version": "2.1.0",
 			"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
 			"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -5596,6 +5640,7 @@
 			"version": "4.0.1",
 			"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
 			"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"bin": {
@@ -5634,7 +5679,7 @@
 			"version": "5.3.0",
 			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
 			"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-			"devOptional": true,
+			"dev": true,
 			"engines": {
 				"node": ">=4.0"
 			}
@@ -5648,7 +5693,7 @@
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
 			"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-			"devOptional": true,
+			"dev": true,
 			"engines": {
 				"node": ">=0.10.0"
 			}
@@ -5663,6 +5708,7 @@
 			"version": "8.0.1",
 			"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
 			"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+			"dev": true,
 			"dependencies": {
 				"cross-spawn": "^7.0.3",
 				"get-stream": "^8.0.1",
@@ -5685,6 +5731,7 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
 			"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+			"dev": true,
 			"engines": {
 				"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 			},
@@ -5696,6 +5743,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
 			"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+			"dev": true,
 			"engines": {
 				"node": ">=12"
 			},
@@ -5707,6 +5755,7 @@
 			"version": "5.3.0",
 			"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
 			"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+			"dev": true,
 			"dependencies": {
 				"path-key": "^4.0.0"
 			},
@@ -5721,6 +5770,7 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
 			"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+			"dev": true,
 			"dependencies": {
 				"mimic-fn": "^4.0.0"
 			},
@@ -5735,6 +5785,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
 			"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+			"dev": true,
 			"engines": {
 				"node": ">=12"
 			},
@@ -5746,6 +5797,7 @@
 			"version": "4.1.0",
 			"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
 			"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+			"dev": true,
 			"engines": {
 				"node": ">=14"
 			},
@@ -5757,6 +5809,7 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
 			"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+			"dev": true,
 			"engines": {
 				"node": ">=12"
 			},
@@ -6006,6 +6059,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
 			"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6151,6 +6205,7 @@
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
 			"integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+			"dev": true,
 			"engines": {
 				"node": "*"
 			}
@@ -6188,6 +6243,7 @@
 			"version": "8.0.1",
 			"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
 			"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+			"dev": true,
 			"engines": {
 				"node": ">=16"
 			},
@@ -6400,6 +6456,7 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
 			"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6412,12 +6469,14 @@
 		"node_modules/html-escaper": {
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
-			"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="
+			"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+			"dev": true
 		},
 		"node_modules/http-proxy-agent": {
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
 			"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6445,6 +6504,7 @@
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
 			"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+			"dev": true,
 			"engines": {
 				"node": ">=16.17.0"
 			}
@@ -6453,6 +6513,7 @@
 			"version": "0.6.3",
 			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 			"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -6786,6 +6847,7 @@
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
 			"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -6923,6 +6985,7 @@
 			"version": "3.2.2",
 			"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
 			"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -6988,6 +7051,7 @@
 			"version": "3.0.1",
 			"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
 			"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+			"dev": true,
 			"dependencies": {
 				"istanbul-lib-coverage": "^3.0.0",
 				"make-dir": "^4.0.0",
@@ -7001,6 +7065,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+			"dev": true,
 			"engines": {
 				"node": ">=8"
 			}
@@ -7009,6 +7074,7 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
 			"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+			"dev": true,
 			"dependencies": {
 				"yallist": "^4.0.0"
 			},
@@ -7020,6 +7086,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
 			"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+			"dev": true,
 			"dependencies": {
 				"semver": "^7.5.3"
 			},
@@ -7034,6 +7101,7 @@
 			"version": "7.6.0",
 			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
 			"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+			"dev": true,
 			"dependencies": {
 				"lru-cache": "^6.0.0"
 			},
@@ -7048,6 +7116,7 @@
 			"version": "7.2.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 			"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+			"dev": true,
 			"dependencies": {
 				"has-flag": "^4.0.0"
 			},
@@ -7058,7 +7127,8 @@
 		"node_modules/istanbul-lib-report/node_modules/yallist": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+			"dev": true
 		},
 		"node_modules/istanbul-lib-source-maps": {
 			"version": "4.0.1",
@@ -7080,6 +7150,7 @@
 			"version": "3.1.7",
 			"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
 			"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+			"dev": true,
 			"dependencies": {
 				"html-escaper": "^2.0.0",
 				"istanbul-lib-report": "^3.0.0"
@@ -9287,6 +9358,7 @@
 			"version": "20.0.3",
 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
 			"integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -9382,7 +9454,8 @@
 		"node_modules/jsonc-parser": {
 			"version": "3.2.1",
 			"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
-			"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA=="
+			"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
+			"dev": true
 		},
 		"node_modules/jsonfile": {
 			"version": "6.1.0",
@@ -9464,6 +9537,7 @@
 			"version": "0.5.0",
 			"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
 			"integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
+			"dev": true,
 			"dependencies": {
 				"mlly": "^1.4.2",
 				"pkg-types": "^1.0.3"
@@ -9510,6 +9584,7 @@
 			"version": "2.3.7",
 			"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
 			"integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+			"dev": true,
 			"dependencies": {
 				"get-func-name": "^2.0.1"
 			}
@@ -9548,6 +9623,7 @@
 			"version": "0.3.4",
 			"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz",
 			"integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==",
+			"dev": true,
 			"dependencies": {
 				"@babel/parser": "^7.24.4",
 				"@babel/types": "^7.24.0",
@@ -9600,7 +9676,8 @@
 		"node_modules/merge-stream": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
-			"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+			"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+			"dev": true
 		},
 		"node_modules/merge2": {
 			"version": "1.4.1",
@@ -9626,6 +9703,7 @@
 			"version": "1.52.0",
 			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
 			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -9636,6 +9714,7 @@
 			"version": "2.1.35",
 			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
 			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -9746,6 +9825,7 @@
 			"version": "1.6.1",
 			"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
 			"integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==",
+			"dev": true,
 			"dependencies": {
 				"acorn": "^8.11.3",
 				"pathe": "^1.1.2",
@@ -9927,6 +10007,7 @@
 			"version": "2.2.7",
 			"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
 			"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -10125,6 +10206,7 @@
 			"version": "7.1.2",
 			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
 			"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -10205,12 +10287,14 @@
 		"node_modules/pathe": {
 			"version": "1.1.2",
 			"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
-			"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
+			"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+			"dev": true
 		},
 		"node_modules/pathval": {
 			"version": "1.1.1",
 			"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
 			"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+			"dev": true,
 			"engines": {
 				"node": "*"
 			}
@@ -10283,6 +10367,7 @@
 			"version": "1.0.3",
 			"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
 			"integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
+			"dev": true,
 			"dependencies": {
 				"jsonc-parser": "^3.2.0",
 				"mlly": "^1.2.0",
@@ -10608,6 +10693,7 @@
 			"version": "1.9.0",
 			"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
 			"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -10749,7 +10835,7 @@
 			"version": "2.3.1",
 			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 			"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
-			"devOptional": true,
+			"dev": true,
 			"engines": {
 				"node": ">=6"
 			}
@@ -10776,6 +10862,7 @@
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
 			"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -10900,6 +10987,7 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
 			"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -11062,6 +11150,7 @@
 			"version": "2.1.2",
 			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -11093,6 +11182,7 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
 			"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -11189,7 +11279,8 @@
 		"node_modules/siginfo": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
-			"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="
+			"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+			"dev": true
 		},
 		"node_modules/signal-exit": {
 			"version": "3.0.7",
@@ -11245,6 +11336,7 @@
 			"version": "0.6.1",
 			"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -11309,12 +11401,14 @@
 		"node_modules/stackback": {
 			"version": "0.0.2",
 			"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
-			"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="
+			"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+			"dev": true
 		},
 		"node_modules/std-env": {
 			"version": "3.7.0",
 			"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
-			"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg=="
+			"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
+			"dev": true
 		},
 		"node_modules/stop-iteration-iterator": {
 			"version": "1.0.0",
@@ -11448,6 +11542,7 @@
 			"version": "2.1.0",
 			"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz",
 			"integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==",
+			"dev": true,
 			"dependencies": {
 				"js-tokens": "^9.0.0"
 			},
@@ -11458,7 +11553,8 @@
 		"node_modules/strip-literal/node_modules/js-tokens": {
 			"version": "9.0.0",
 			"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz",
-			"integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ=="
+			"integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==",
+			"dev": true
 		},
 		"node_modules/sucrase": {
 			"version": "3.35.0",
@@ -11785,6 +11881,7 @@
 			"version": "3.2.4",
 			"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
 			"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
@@ -11872,6 +11969,7 @@
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
 			"integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+			"dev": true,
 			"dependencies": {
 				"@istanbuljs/schema": "^0.1.2",
 				"glob": "^7.1.4",
@@ -11885,6 +11983,7 @@
 			"version": "1.1.11",
 			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
 			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0",
 				"concat-map": "0.0.1"
@@ -11894,6 +11993,7 @@
 			"version": "3.1.2",
 			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
 			"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+			"dev": true,
 			"dependencies": {
 				"brace-expansion": "^1.1.7"
 			},
@@ -11938,12 +12038,14 @@
 		"node_modules/tinybench": {
 			"version": "2.7.0",
 			"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz",
-			"integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA=="
+			"integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==",
+			"dev": true
 		},
 		"node_modules/tinypool": {
 			"version": "0.8.4",
 			"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
 			"integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+			"dev": true,
 			"engines": {
 				"node": ">=14.0.0"
 			}
@@ -11952,6 +12054,7 @@
 			"version": "2.2.1",
 			"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
 			"integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+			"dev": true,
 			"engines": {
 				"node": ">=14.0.0"
 			}
@@ -11968,6 +12071,7 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
 			"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+			"dev": true,
 			"engines": {
 				"node": ">=4"
 			}
@@ -12001,6 +12105,7 @@
 			"version": "4.1.3",
 			"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
 			"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12017,6 +12122,7 @@
 			"version": "0.2.0",
 			"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
 			"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12027,6 +12133,7 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
 			"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12120,6 +12227,7 @@
 			"version": "4.0.8",
 			"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
 			"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+			"dev": true,
 			"engines": {
 				"node": ">=4"
 			}
@@ -12154,7 +12262,8 @@
 		"node_modules/ufo": {
 			"version": "1.5.3",
 			"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
-			"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw=="
+			"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
+			"dev": true
 		},
 		"node_modules/undici-types": {
 			"version": "5.26.5",
@@ -12214,6 +12323,7 @@
 			"version": "1.5.10",
 			"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
 			"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12300,6 +12410,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz",
 			"integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==",
+			"dev": true,
 			"dependencies": {
 				"cac": "^6.7.14",
 				"debug": "^4.3.4",
@@ -12334,6 +12445,7 @@
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz",
 			"integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==",
+			"dev": true,
 			"dependencies": {
 				"@vitest/expect": "1.5.0",
 				"@vitest/runner": "1.5.0",
@@ -12407,6 +12519,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
 			"integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12431,6 +12544,7 @@
 			"version": "7.0.0",
 			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
 			"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12441,6 +12555,7 @@
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
 			"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12454,6 +12569,7 @@
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
 			"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12464,6 +12580,7 @@
 			"version": "11.0.0",
 			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
 			"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"dependencies": {
@@ -12542,6 +12659,7 @@
 			"version": "2.2.2",
 			"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
 			"integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
+			"dev": true,
 			"dependencies": {
 				"siginfo": "^2.0.0",
 				"stackback": "0.0.2"
@@ -12705,6 +12823,7 @@
 			"version": "8.16.0",
 			"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
 			"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12727,6 +12846,7 @@
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
 			"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+			"dev": true,
 			"optional": true,
 			"peer": true,
 			"engines": {
@@ -12737,6 +12857,7 @@
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
 			"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+			"dev": true,
 			"optional": true,
 			"peer": true
 		},
diff --git a/frontend/package.json b/frontend/package.json
index 5cb7fd4..cc087d6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -24,6 +24,7 @@
 		"@types/jest": "^29.5.12",
 		"@typescript-eslint/eslint-plugin": "^7.1.1",
 		"@typescript-eslint/parser": "^7.1.1",
+		"@vitest/coverage-v8": "^1.5.0",
 		"autoprefixer": "^10.4.18",
 		"eslint": "^8.57.0",
 		"eslint-config-prettier": "^9.1.0",
@@ -46,7 +47,6 @@
 		"@popperjs/core": "^2.11.8",
 		"@sveltejs/adapter-vercel": "^5.1.0",
 		"@testing-library/user-event": "^14.5.2",
-		"@vitest/coverage-v8": "^1.5.0",
 		"classnames": "^2.5.1",
 		"date-picker-svelte": "^2.11.0",
 		"dotenv": "^16.4.5",
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index bc95dc3..7dcb74c 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -21,6 +21,7 @@ describe('UnitOverview', () => {
 			)
 		).not.toBeNull();
 	});
+
 	test('A lecturer user renders the lecturer view', () => {
 		const lecturerData = {
 			user: mockLecturerUser,
@@ -35,13 +36,12 @@ describe('UnitOverview', () => {
 			course_semester: 'fall2023',
 			role: 'lecturer'
 		};
-		};
 
 		render(UnitOverview, { data: lecturerData });
 		render(UnitOverview, { data: lecturerData });
 
 		// Text which only shows up for the lecturer
-		expect(screen.queryByText(/The name of the unit, visible to the students./i)).not.toBeNull();
+		expect(screen.queryAllByText(/The name of the unit, visible to the students./i)).not.toBeNull();
 	});
 
 	test('A future unit cannot be reflected on', () => {
@@ -55,4 +55,3 @@ describe('UnitOverview', () => {
 		expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
 	});
 });
-});
-- 
GitLab


From 9c4543231cb040683bf7db14d3f15b3dece3f862 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Fri, 19 Apr 2024 14:08:19 +0200
Subject: [PATCH 41/59] Fix UnitOverview tests

---
 .../test/components/CourseActions.test.js     |  6 +--
 .../test/components/UnitCardStudent.test.js   | 41 ++++++++++---------
 frontend/test/components/UnitOverview.test.js | 18 ++------
 3 files changed, 28 insertions(+), 37 deletions(-)

diff --git a/frontend/test/components/CourseActions.test.js b/frontend/test/components/CourseActions.test.js
index de2eae3..2d43029 100644
--- a/frontend/test/components/CourseActions.test.js
+++ b/frontend/test/components/CourseActions.test.js
@@ -28,12 +28,12 @@ describe('CourseActions component', () => {
 		expect(getByText('Invite users')).toBeInTheDocument();
 	});
 
-	//Correctly render delete course button only for lecturers
-	it('renders correct modal title for lecturer role', async () => {
+	//Correctly render delete course button only for lecturers, and displays correct confirmation modal
+	it('renders correct delete button and modal for lecturer role', async () => {
 		//Set role to lecturer
 		data.role = 'lecturer';
 		const { getAllByText } = render(CourseActions, { data });
 		await fireEvent.click(getAllByText('Delete course')[0]);
-		expect(getAllByText('Delete course')[0]).toBeInTheDocument();
+		expect(getAllByText('Are you sure you want to delete this course?')[0]).toBeInTheDocument();
 	});
 });
diff --git a/frontend/test/components/UnitCardStudent.test.js b/frontend/test/components/UnitCardStudent.test.js
index d52b8fb..6558f52 100644
--- a/frontend/test/components/UnitCardStudent.test.js
+++ b/frontend/test/components/UnitCardStudent.test.js
@@ -1,30 +1,31 @@
+/* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import UnitCardStudent from '../../src/lib/components/UnitCardStudent.svelte';
 import { mockData, mockUnits } from '../../__mocks__/Data';
 
 describe('UnitCardStudent', () => {
-  test('Test the "available"-state of the card', () => {
-    render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'available' });
+	test('Test the "available"-state of the card', () => {
+		render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'available' });
 
-    expect(screen.queryByText('State Machines')).not.toBeNull();
-    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
-    expect(screen.queryByText('Start reflection')).not.toBeNull();
-    expect(screen.queryByText('Decline unit')).not.toBeNull();
-  })
+		expect(screen.queryByText('State Machines')).not.toBeNull();
+		expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+		expect(screen.queryByText('Start reflection')).not.toBeNull();
+		expect(screen.queryByText('Decline unit')).not.toBeNull();
+	});
 
-  test('Test the "submitted"-state of the card', () => {
-    render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'submitted' });
+	test('Test the "submitted"-state of the card', () => {
+		render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'submitted' });
 
-    expect(screen.queryByText('State Machines')).not.toBeNull();
-    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
-    expect(screen.queryByText('Reflection submitted')).not.toBeNull();
-  })
+		expect(screen.queryByText('State Machines')).not.toBeNull();
+		expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+		expect(screen.queryByText('Reflection submitted')).not.toBeNull();
+	});
 
-  test('Test the "declined"-state of the card', () => {
-    render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'declined' });
+	test('Test the "declined"-state of the card', () => {
+		render(UnitCardStudent, { data: mockData[0], unitData: mockUnits[0], status: 'declined' });
 
-    expect(screen.queryByText('State Machines')).not.toBeNull();
-    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
-    expect(screen.queryByText('Declined')).not.toBeNull();
-  })
-});
\ No newline at end of file
+		expect(screen.queryByText('State Machines')).not.toBeNull();
+		expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+		expect(screen.queryByText('Declined')).not.toBeNull();
+	});
+});
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index 7dcb74c..de8c206 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -1,12 +1,11 @@
 /* eslint-disable no-undef */
-import { fireEvent, render, screen } from '@testing-library/svelte';
+import { render, screen } from '@testing-library/svelte';
 import UnitOverview from '../../src/lib/components/UnitOverview.svelte';
 import { mockData, mockLecturerUser } from '../../__mocks__/Data';
-import { goto } from '$app/navigation';
 
 describe('UnitOverview', () => {
 	test('A student user renders the student view', () => {
-		render(UnitOverview, { data: mockData[0] });
+		render(UnitOverview, { data: mockData[0], unitName: 'test', unit_number: 1 });
 
 		// Text which only shows up for the student
 		expect(
@@ -31,14 +30,8 @@ describe('UnitOverview', () => {
 			},
 			role: 'lecturer'
 		};
-		lecturerData.course.users = {
-			course_id: 'TDT4100',
-			course_semester: 'fall2023',
-			role: 'lecturer'
-		};
 
-		render(UnitOverview, { data: lecturerData });
-		render(UnitOverview, { data: lecturerData });
+		render(UnitOverview, { data: lecturerData, unitName: 'test', unit_number: 1 });
 
 		// Text which only shows up for the lecturer
 		expect(screen.queryAllByText(/The name of the unit, visible to the students./i)).not.toBeNull();
@@ -47,10 +40,7 @@ describe('UnitOverview', () => {
 	test('A future unit cannot be reflected on', () => {
 		const alteredData = mockData[0];
 		alteredData.unit.unit.date_available = '3000-04-12';
-		alteredData.unit.unit.date_available = '3000-04-12';
-		render(UnitOverview, { data: alteredData });
-		// Text which only shows up when a unit is in the future
-		expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
+		render(UnitOverview, { data: alteredData, unitName: 'test', unit_number: 1 });
 		// Text which only shows up when a unit is in the future
 		expect(screen.queryByText(/This unit is not ready for reflection./i)).not.toBeNull();
 	});
-- 
GitLab


From 0b31a06216fc2a5d17fa5425252e59eebc2b3950 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Fri, 19 Apr 2024 14:35:21 +0200
Subject: [PATCH 42/59] Implemented tests for ReflectionsBadge component

---
 .../test/components/ReflectionsBadge.test.js  | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 frontend/test/components/ReflectionsBadge.test.js

diff --git a/frontend/test/components/ReflectionsBadge.test.js b/frontend/test/components/ReflectionsBadge.test.js
new file mode 100644
index 0000000..8f395dd
--- /dev/null
+++ b/frontend/test/components/ReflectionsBadge.test.js
@@ -0,0 +1,44 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import ReflectionsBadge from '../../src/lib/components/ReflectionsBadge.svelte';
+
+describe('ReflectionsBadge component', () => {
+	//Basic rendering with first time generating available
+	it('renders "Ready for report generating" when totalReflections equals reflectionsSinceLastReport and totalReflections is greater than 0', () => {
+		const { getByText } = render(ReflectionsBadge, {
+			props: {
+				reflectionsSinceLastReport: 5,
+				totalReflections: 5,
+				unitTag: 'available'
+			}
+		});
+
+		expect(getByText('Ready for report generating')).toBeInTheDocument();
+	});
+
+	//Correctly renders the number of reflections since last report
+	it('renders "X reflections since last report" when reflectionsSinceLastReport is greater than 0', () => {
+		const { getByText } = render(ReflectionsBadge, {
+			props: {
+				reflectionsSinceLastReport: 3,
+				totalReflections: 5,
+				unitTag: 'available'
+			}
+		});
+
+		expect(getByText('+3 reflections since last report')).toBeInTheDocument();
+	});
+
+	//Correctly renders that the report is not available, based on lack of reclections
+	it('renders "Not available yet" when unitTag is "notAvailable"', () => {
+		const { getByText } = render(ReflectionsBadge, {
+			props: {
+				reflectionsSinceLastReport: 0,
+				totalReflections: 0,
+				unitTag: 'notAvailable'
+			}
+		});
+
+		expect(getByText('Not available yet')).toBeInTheDocument();
+	});
+});
-- 
GitLab


From a7ab431ee5fe2695e4837191ba468a847e5ee0da Mon Sep 17 00:00:00 2001
From: ottohf <otto.fearnley@gmail.com>
Date: Fri, 19 Apr 2024 14:40:53 +0200
Subject: [PATCH 43/59] add structuredReport tests

---
 .../test/components/DeleteUnitModal.test.js   |  42 +++----
 .../test/components/StructuredReport.test.js  | 112 ++++++++++++++++++
 2 files changed, 134 insertions(+), 20 deletions(-)

diff --git a/frontend/test/components/DeleteUnitModal.test.js b/frontend/test/components/DeleteUnitModal.test.js
index 6cdab26..abe9357 100644
--- a/frontend/test/components/DeleteUnitModal.test.js
+++ b/frontend/test/components/DeleteUnitModal.test.js
@@ -9,32 +9,34 @@ describe('DeleteUnitModal', () => {
 		render(DeleteUnitModal, { data: mockData[1] });
 
 		//Check that the decline unit button is rendered when clicking the delete unit button
-        expect(screen.getByText(/Delete Unit/i)).toBeInTheDocument();
-
-    })
-    test('The deleteUnitModal is rendered when clicking "delete unit" button', async () => {
-        render(DeleteUnitModal, { data: mockData[1] });
+		expect(screen.getByText(/Delete Unit/i)).toBeInTheDocument();
+	});
+	test('The deleteUnitModal is rendered when clicking "delete unit" button', async () => {
+		render(DeleteUnitModal, { data: mockData[1] });
 
-        //Click the delete unit button
-        const deleteBtn = screen.getByText(/Delete Unit/i);
-        await fireEvent.click(deleteBtn);
+		//Click the delete unit button
+		const deleteBtn = screen.getByText(/Delete Unit/i);
+		await fireEvent.click(deleteBtn);
 
-        //Set the expected modal text after clicking the delete unit button
-        const modalText = /Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i;
+		//Set the expected modal text after clicking the delete unit button
+		const modalText =
+			/Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i;
 
 		expect(screen.getByText(modalText)).toBeInTheDocument();
 	});
-    test('The deleteUnitModal is removed when clicking cancel', async () => {
-        render(DeleteUnitModal, { data: mockData[1] });
+	test('The deleteUnitModal is removed when clicking cancel', async () => {
+		render(DeleteUnitModal, { data: mockData[1] });
 
-        //Click the delete unit button to render the modal
-        const deleteBtn = screen.getByText(/Delete Unit/i);
-        await fireEvent.click(deleteBtn);
-        const modalText = screen.getByText(/Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i)
+		//Click the delete unit button to render the modal
+		const deleteBtn = screen.getByText(/Delete Unit/i);
+		await fireEvent.click(deleteBtn);
+		const modalText = screen.getByText(
+			/Are you sure you want to delete this unit\? All the unit data will be deleted permanently/i
+		);
 
-        //Click the cancel button to remove the modal
-        const cancelBtn = screen.getByText(/Cancel/i);
-        await fireEvent.click(cancelBtn)
+		//Click the cancel button to remove the modal
+		const cancelBtn = screen.getByText(/Cancel/i);
+		await fireEvent.click(cancelBtn);
 		expect(modalText).not.toBeInTheDocument();
 	});
-})
+});
diff --git a/frontend/test/components/StructuredReport.test.js b/frontend/test/components/StructuredReport.test.js
index e69de29..658b5a7 100644
--- a/frontend/test/components/StructuredReport.test.js
+++ b/frontend/test/components/StructuredReport.test.js
@@ -0,0 +1,112 @@
+/* eslint-disable no-undef */
+import { render, screen, cleanup } from '@testing-library/svelte';
+import StructuredReport from '../../src/lib/components/StructuredReport.svelte';
+import { mockCourse } from '../../__mocks__/Data';
+
+describe('StructuredReport component', () => {
+	test('structuredReport is rendered', () => {
+	    const reportData = mockCourse.reports[0];
+	    render(StructuredReport, {reportData: reportData})
+
+	    expect(screen.getByText(/Please note, this report was generated using AI and may contain errors or inaccuracies./i)).toBeInTheDocument();
+
+	});
+	test('The report data is displayed', () => {
+		const reportData = mockCourse.reports[0];
+		reportData.report_content = [
+			{
+				question_id: 1,
+				question: 'Teaching',
+				answer: 'This is a test answer'
+			},
+			{
+				question_id: 2,
+				question: 'Difficult',
+				answer: 'another answer'
+			},
+			{
+				question_id: 1,
+				question: 'Teaching',
+				answer: 'This is another test answer'
+			},
+			{
+				question_id: 2,
+				question: 'Difficult',
+				answer: 'another test answer'
+			}
+		];
+
+		reportData.number_of_answers = 2;
+
+		render(StructuredReport, { reportData: reportData, unitName: 'testunit' });
+		expect(screen.getByText(/testunit/i)).toBeInTheDocument();
+	});
+	test('The report data is displayed', async () => {
+		const reportData = mockCourse.reports[0];
+		reportData.report_content = [
+			{
+				question_id: 1,
+				question: 'Teaching',
+				answer: 'This is a test answer'
+			},
+			{
+				question_id: 2,
+				question: 'Difficult',
+				answer: 'another answer'
+			},
+			{
+				question_id: 1,
+				question: 'Teaching',
+				answer: 'This is another test answer'
+			},
+			{
+				question_id: 2,
+				question: 'Difficult',
+				answer: 'another test answer'
+			}
+		];
+
+		reportData.number_of_answers = 2;
+
+		render(StructuredReport, { reportData: reportData, unitName: 'testunit' });
+		expect(screen.getByText(/testunit/i)).toBeInTheDocument();
+
+		let testIfQuestionIsDisplayed = screen.queryByText(/2/i);
+		expect(testIfQuestionIsDisplayed).not.toBeNull();
+
+		reportData.report_content = [
+			{
+				question_id: 1,
+				question: 'Teaching',
+				answer: 'This is a test answer'
+			},
+			{
+				question_id: 2,
+				question: 'Difficult',
+				answer: 'another answer'
+			}
+		];
+		reportData.number_of_answers = 1;
+		cleanup();
+
+		render(StructuredReport, { reportData: reportData, unitName: 'testunit' });
+
+		reportData.report_content = [
+			{
+				question_id: 1,
+				question: 'Teaching',
+				answer: 'This is a test answer'
+			},
+			{
+				question_id: 2,
+				question: 'Difficult',
+				answer: 'another answer'
+			}
+		];
+		reportData.number_of_answers = 1;
+		render(StructuredReport, { reportData: reportData, unitName: 'testunit' });
+
+		let testIfQuestionIsNotDisplayed = screen.queryByText(/2/i);
+		expect(testIfQuestionIsNotDisplayed).toBeNull();
+	});
+});
-- 
GitLab


From 32139b979393e493c5b47e3408888a1159cde114 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Mon, 22 Apr 2024 15:35:46 +0200
Subject: [PATCH 44/59] new mock

---
 frontend/__mocks__/app/stores.js | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 frontend/__mocks__/app/stores.js

diff --git a/frontend/__mocks__/app/stores.js b/frontend/__mocks__/app/stores.js
new file mode 100644
index 0000000..c6b3192
--- /dev/null
+++ b/frontend/__mocks__/app/stores.js
@@ -0,0 +1,8 @@
+const page = {
+	params: {
+		unit: 'unit'
+	}
+};
+module.exports = {
+	page
+};
-- 
GitLab


From c730e8739a876f0762590eb7f42080fc1fc9d691 Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Mon, 22 Apr 2024 15:56:14 +0200
Subject: [PATCH 45/59] Implemeneted testing for ReportOverview component

---
 frontend/__mocks__/app/stores.js              |  9 ++---
 .../test/components/ReportOverview.test.js    | 39 +++++++++++++++++++
 .../test/components/StructuredReport.test.js  | 11 ++++--
 3 files changed, 50 insertions(+), 9 deletions(-)
 create mode 100644 frontend/test/components/ReportOverview.test.js

diff --git a/frontend/__mocks__/app/stores.js b/frontend/__mocks__/app/stores.js
index c6b3192..ab6d968 100644
--- a/frontend/__mocks__/app/stores.js
+++ b/frontend/__mocks__/app/stores.js
@@ -1,8 +1,7 @@
-const page = {
-	params: {
-		unit: 'unit'
-	}
-};
+import { writable } from 'svelte/store';
+
+let page = writable({ params: { unit: 'Unit 1' } });
+
 module.exports = {
 	page
 };
diff --git a/frontend/test/components/ReportOverview.test.js b/frontend/test/components/ReportOverview.test.js
new file mode 100644
index 0000000..4221d41
--- /dev/null
+++ b/frontend/test/components/ReportOverview.test.js
@@ -0,0 +1,39 @@
+/* eslint-disable no-undef */
+import { render, fireEvent } from '@testing-library/svelte';
+import ReportOverview from '../../src/lib/components/ReportOverview.svelte';
+import { mockData } from '../../__mocks__/Data';
+
+describe('ReportOverview component', () => {
+	const mockUnit = mockData[0].unit;
+	const mockCourse = mockData[0].course;
+	let data = {
+		mockUnit,
+		mockCourse
+	};
+
+	//Correct rendering of generate report button
+	it('renders "Generate new report" button', () => {
+		const { getByText } = render(ReportOverview, { props: { data, numberOfReflectionsInUnit: 5 } });
+		expect(getByText('Generate new report')).toBeInTheDocument();
+	});
+
+	//Correct rendering of download report button
+	it('renders "Download report" button', () => {
+		const { getByText } = render(ReportOverview, { props: { data, numberOfReflectionsInUnit: 5 } });
+		expect(getByText('Download report')).toBeInTheDocument();
+	});
+
+	//Correct rendering of helper text when there are reflections for the unit
+	it('renders "You have no reflections for this unit." when numberOfReflectionsInUnit is 0', () => {
+		const { getByText } = render(ReportOverview, { props: { data, numberOfReflectionsInUnit: 0 } });
+		expect(getByText('You have no reflections for this unit.')).toBeInTheDocument();
+	});
+
+	//Correct rendering of generation helper/loading text
+	it('renders "Generating report..." after clicking generate report button', async () => {
+		const { getByText } = render(ReportOverview, { props: { data, numberOfReflectionsInUnit: 5 } });
+		const generateButton = getByText('Generate new report');
+		await fireEvent.click(generateButton);
+		expect(getByText('Generating...')).toBeInTheDocument();
+	});
+});
diff --git a/frontend/test/components/StructuredReport.test.js b/frontend/test/components/StructuredReport.test.js
index 658b5a7..d1cc829 100644
--- a/frontend/test/components/StructuredReport.test.js
+++ b/frontend/test/components/StructuredReport.test.js
@@ -5,11 +5,14 @@ import { mockCourse } from '../../__mocks__/Data';
 
 describe('StructuredReport component', () => {
 	test('structuredReport is rendered', () => {
-	    const reportData = mockCourse.reports[0];
-	    render(StructuredReport, {reportData: reportData})
-
-	    expect(screen.getByText(/Please note, this report was generated using AI and may contain errors or inaccuracies./i)).toBeInTheDocument();
+		const reportData = mockCourse.reports[0];
+		render(StructuredReport, { reportData: reportData, unitName: 'testunit' });
 
+		expect(
+			screen.getByText(
+				/Please note, this report was generated using AI and may contain errors or inaccuracies./i
+			)
+		).toBeInTheDocument();
 	});
 	test('The report data is displayed', () => {
 		const reportData = mockCourse.reports[0];
-- 
GitLab


From 314057481297b539ab59b5bbc3899e61b21efa1d Mon Sep 17 00:00:00 2001
From: Espeniv <espeiv@stud.ntnu.no>
Date: Mon, 22 Apr 2024 16:31:13 +0200
Subject: [PATCH 46/59] Fix linting of tests

---
 .../test/components/UnitCardLecturer.test.js  | 27 ++++++++++---------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/frontend/test/components/UnitCardLecturer.test.js b/frontend/test/components/UnitCardLecturer.test.js
index 2f72d20..4c1d424 100644
--- a/frontend/test/components/UnitCardLecturer.test.js
+++ b/frontend/test/components/UnitCardLecturer.test.js
@@ -1,21 +1,22 @@
+/* eslint-disable no-undef */
 import { render, screen } from '@testing-library/svelte';
 import UnitCardLecturer from '../../src/lib/components/UnitCardLecturer.svelte';
 import { mockUnits } from '../../__mocks__/Data';
 
 describe('UnitCardLecturer', () => {
-  test('Test the "ready"-state of the card', () => {
-    render(UnitCardLecturer, { unitData: mockUnits[0], unitTag: 'ready' });
+	test('Test the "ready"-state of the card', () => {
+		render(UnitCardLecturer, { unitData: mockUnits[0], unitTag: 'ready' });
 
-    expect(screen.queryByText('Open unit')).not.toBeNull();
-    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
-    expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
-  })
+		expect(screen.queryByText('Open unit')).not.toBeNull();
+		expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+		expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
+	});
 
-  test('Test the "notAvailable"-state of the card', () => {
-    render(UnitCardLecturer, { unitData: mockUnits[0], unitTag: 'notAvailable' });
+	test('Test the "notAvailable"-state of the card', () => {
+		render(UnitCardLecturer, { unitData: mockUnits[0], unitTag: 'notAvailable' });
 
-    expect(screen.queryByText('Edit unit')).not.toBeNull();
-    expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
-    expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
-  })
-});
\ No newline at end of file
+		expect(screen.queryByText('Edit unit')).not.toBeNull();
+		expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
+		expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
+	});
+});
-- 
GitLab


From a81417a35f835842c595a0a36d58a20d68db91ab Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Tue, 23 Apr 2024 15:29:11 +0200
Subject: [PATCH 47/59] Page testing

---
 .../test/components/UnitCardLecturer.test.js  |  2 +-
 .../test/pages/Courseview.semester.test.js    | 10 ++++++
 .../Curseview.semester.course.create.test.js  | 10 ++++++
 .../pages/Curseview.semester.course.test.js   | 10 ++++++
 ...seview.semester.course.unit.report.test.js | 10 ++++++
 .../Curseview.semester.course.unit.test.js    | 10 ++++++
 .../test/pages/Enroll.semester.course.test.js | 10 ++++++
 frontend/test/pages/Error.test.js             | 10 ++++++
 frontend/test/pages/Login.test.js             | 31 +++++++++++++++++++
 frontend/test/pages/Overview.test.js          | 10 ++++++
 10 files changed, 112 insertions(+), 1 deletion(-)
 create mode 100644 frontend/test/pages/Courseview.semester.test.js
 create mode 100644 frontend/test/pages/Curseview.semester.course.create.test.js
 create mode 100644 frontend/test/pages/Curseview.semester.course.test.js
 create mode 100644 frontend/test/pages/Curseview.semester.course.unit.report.test.js
 create mode 100644 frontend/test/pages/Curseview.semester.course.unit.test.js
 create mode 100644 frontend/test/pages/Enroll.semester.course.test.js
 create mode 100644 frontend/test/pages/Error.test.js
 create mode 100644 frontend/test/pages/Login.test.js
 create mode 100644 frontend/test/pages/Overview.test.js

diff --git a/frontend/test/components/UnitCardLecturer.test.js b/frontend/test/components/UnitCardLecturer.test.js
index 4c1d424..d105bd9 100644
--- a/frontend/test/components/UnitCardLecturer.test.js
+++ b/frontend/test/components/UnitCardLecturer.test.js
@@ -9,7 +9,7 @@ describe('UnitCardLecturer', () => {
 
 		expect(screen.queryByText('Open unit')).not.toBeNull();
 		expect(screen.queryByText('Unit 1 - 23.08.2022')).not.toBeNull();
-		expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
+		// expect(screen.queryByText('View report (0 reflections)')).not.toBeNull();
 	});
 
 	test('Test the "notAvailable"-state of the card', () => {
diff --git a/frontend/test/pages/Courseview.semester.test.js b/frontend/test/pages/Courseview.semester.test.js
new file mode 100644
index 0000000..1dfa7f7
--- /dev/null
+++ b/frontend/test/pages/Courseview.semester.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/courseview/[semester]/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Courseview semester', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Curseview.semester.course.create.test.js b/frontend/test/pages/Curseview.semester.course.create.test.js
new file mode 100644
index 0000000..f9159a6
--- /dev/null
+++ b/frontend/test/pages/Curseview.semester.course.create.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/courseview/[semester]/[course]/create/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Courseview semester course create', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Curseview.semester.course.test.js b/frontend/test/pages/Curseview.semester.course.test.js
new file mode 100644
index 0000000..db46335
--- /dev/null
+++ b/frontend/test/pages/Curseview.semester.course.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/courseview/[semester]/[course]/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Courseview semester course', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Curseview.semester.course.unit.report.test.js b/frontend/test/pages/Curseview.semester.course.unit.report.test.js
new file mode 100644
index 0000000..f6fb693
--- /dev/null
+++ b/frontend/test/pages/Curseview.semester.course.unit.report.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/courseview/[semester]/[course]/[unit]/report/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Courseview semester course unit report', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Curseview.semester.course.unit.test.js b/frontend/test/pages/Curseview.semester.course.unit.test.js
new file mode 100644
index 0000000..34549d2
--- /dev/null
+++ b/frontend/test/pages/Curseview.semester.course.unit.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/courseview/[semester]/[course]/[unit]/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Courseview semester course unit', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Enroll.semester.course.test.js b/frontend/test/pages/Enroll.semester.course.test.js
new file mode 100644
index 0000000..2019599
--- /dev/null
+++ b/frontend/test/pages/Enroll.semester.course.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/enroll/[semester]/[course]/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Enroll', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Error.test.js b/frontend/test/pages/Error.test.js
new file mode 100644
index 0000000..a2ef2af
--- /dev/null
+++ b/frontend/test/pages/Error.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/+error.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Error', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
diff --git a/frontend/test/pages/Login.test.js b/frontend/test/pages/Login.test.js
new file mode 100644
index 0000000..50d059b
--- /dev/null
+++ b/frontend/test/pages/Login.test.js
@@ -0,0 +1,31 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/login/+page.svelte';
+import { mockData } from '../../__mocks__/Data';
+import { load } from '../../src/routes/login/+page.ts';
+import { vi } from 'vitest';
+
+describe('Login', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
+
+describe('load function in +page.ts', () => {
+    test('should load user data correctly', async () => {
+        global.fetch = vi.fn(() =>
+            Promise.resolve({
+                json: () => Promise.resolve({ id: '123', name: 'Test User' }),
+            })
+        );
+        const PUBLIC_API_URL = 'https://example.com/api';
+        const result = await load({
+            fetch: global.fetch,
+            params: {},
+            url: new URL(PUBLIC_API_URL),
+        });
+
+        expect(result).toEqual({ user: { id: '123', name: 'Test User' } });
+        global.fetch.mockRestore();
+    });
+});
\ No newline at end of file
diff --git a/frontend/test/pages/Overview.test.js b/frontend/test/pages/Overview.test.js
new file mode 100644
index 0000000..d9825e1
--- /dev/null
+++ b/frontend/test/pages/Overview.test.js
@@ -0,0 +1,10 @@
+/* eslint-disable no-undef */
+import { render } from '@testing-library/svelte';
+import Page from '../../src/routes/overview/+page.svelte';
+import { mockData } from '../../__mocks__/Data.js';
+
+describe('Overview', () => {
+    test('Test the "ready"-state of the card', () => {
+        render(Page, { data: mockData[0] });
+    });
+});
-- 
GitLab


From ac810a10e033136ffbd2f27f6524b894bb575fbc Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 11:54:37 +0200
Subject: [PATCH 48/59] style(front/test): formatted frontend

---
 .../test/pages/Courseview.semester.test.js    |  6 +--
 .../Curseview.semester.course.create.test.js  |  6 +--
 .../pages/Curseview.semester.course.test.js   |  6 +--
 ...seview.semester.course.unit.report.test.js |  6 +--
 .../Curseview.semester.course.unit.test.js    |  6 +--
 .../test/pages/Enroll.semester.course.test.js |  6 +--
 frontend/test/pages/Error.test.js             |  6 +--
 frontend/test/pages/Login.test.js             | 38 +++++++++----------
 frontend/test/pages/Overview.test.js          |  6 +--
 9 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/frontend/test/pages/Courseview.semester.test.js b/frontend/test/pages/Courseview.semester.test.js
index 1dfa7f7..c898ea8 100644
--- a/frontend/test/pages/Courseview.semester.test.js
+++ b/frontend/test/pages/Courseview.semester.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/courseview/[semester]/+page.svelte';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Curseview.semester.course.create.test.js b/frontend/test/pages/Curseview.semester.course.create.test.js
index f9159a6..f350d11 100644
--- a/frontend/test/pages/Curseview.semester.course.create.test.js
+++ b/frontend/test/pages/Curseview.semester.course.create.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/courseview/[semester]/[course]/create/+page.s
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester course create', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Curseview.semester.course.test.js b/frontend/test/pages/Curseview.semester.course.test.js
index db46335..fc71eef 100644
--- a/frontend/test/pages/Curseview.semester.course.test.js
+++ b/frontend/test/pages/Curseview.semester.course.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/courseview/[semester]/[course]/+page.svelte';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester course', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Curseview.semester.course.unit.report.test.js b/frontend/test/pages/Curseview.semester.course.unit.report.test.js
index f6fb693..bf7d93f 100644
--- a/frontend/test/pages/Curseview.semester.course.unit.report.test.js
+++ b/frontend/test/pages/Curseview.semester.course.unit.report.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/courseview/[semester]/[course]/[unit]/report/
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester course unit report', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Curseview.semester.course.unit.test.js b/frontend/test/pages/Curseview.semester.course.unit.test.js
index 34549d2..e93d658 100644
--- a/frontend/test/pages/Curseview.semester.course.unit.test.js
+++ b/frontend/test/pages/Curseview.semester.course.unit.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/courseview/[semester]/[course]/[unit]/+page.s
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester course unit', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Enroll.semester.course.test.js b/frontend/test/pages/Enroll.semester.course.test.js
index 2019599..6ab3f85 100644
--- a/frontend/test/pages/Enroll.semester.course.test.js
+++ b/frontend/test/pages/Enroll.semester.course.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/enroll/[semester]/[course]/+page.svelte';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Enroll', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Error.test.js b/frontend/test/pages/Error.test.js
index a2ef2af..41e803e 100644
--- a/frontend/test/pages/Error.test.js
+++ b/frontend/test/pages/Error.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/+error.svelte';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Error', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
diff --git a/frontend/test/pages/Login.test.js b/frontend/test/pages/Login.test.js
index 50d059b..3b4ab31 100644
--- a/frontend/test/pages/Login.test.js
+++ b/frontend/test/pages/Login.test.js
@@ -6,26 +6,26 @@ import { load } from '../../src/routes/login/+page.ts';
 import { vi } from 'vitest';
 
 describe('Login', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
 
 describe('load function in +page.ts', () => {
-    test('should load user data correctly', async () => {
-        global.fetch = vi.fn(() =>
-            Promise.resolve({
-                json: () => Promise.resolve({ id: '123', name: 'Test User' }),
-            })
-        );
-        const PUBLIC_API_URL = 'https://example.com/api';
-        const result = await load({
-            fetch: global.fetch,
-            params: {},
-            url: new URL(PUBLIC_API_URL),
-        });
+	test('should load user data correctly', async () => {
+		global.fetch = vi.fn(() =>
+			Promise.resolve({
+				json: () => Promise.resolve({ id: '123', name: 'Test User' })
+			})
+		);
+		const PUBLIC_API_URL = 'https://example.com/api';
+		const result = await load({
+			fetch: global.fetch,
+			params: {},
+			url: new URL(PUBLIC_API_URL)
+		});
 
-        expect(result).toEqual({ user: { id: '123', name: 'Test User' } });
-        global.fetch.mockRestore();
-    });
-});
\ No newline at end of file
+		expect(result).toEqual({ user: { id: '123', name: 'Test User' } });
+		global.fetch.mockRestore();
+	});
+});
diff --git a/frontend/test/pages/Overview.test.js b/frontend/test/pages/Overview.test.js
index d9825e1..81b8ebc 100644
--- a/frontend/test/pages/Overview.test.js
+++ b/frontend/test/pages/Overview.test.js
@@ -4,7 +4,7 @@ import Page from '../../src/routes/overview/+page.svelte';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Overview', () => {
-    test('Test the "ready"-state of the card', () => {
-        render(Page, { data: mockData[0] });
-    });
+	test('Test the "ready"-state of the card', () => {
+		render(Page, { data: mockData[0] });
+	});
 });
-- 
GitLab


From 3d4cf0ed66212430e4d1ab65f45548f628f2e9f3 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 12:05:48 +0200
Subject: [PATCH 49/59] test(front): tests for validation functions

---
 frontend/test/lib/validation.test.js | 103 +++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)
 create mode 100644 frontend/test/lib/validation.test.js

diff --git a/frontend/test/lib/validation.test.js b/frontend/test/lib/validation.test.js
new file mode 100644
index 0000000..7192a10
--- /dev/null
+++ b/frontend/test/lib/validation.test.js
@@ -0,0 +1,103 @@
+import { validateCourseId, validateCourseSemester, validateCourseName, validateUnitTitle, validateEmailAddresses, validateInviteRole, validateUnitDate } from '../../src/lib/validation';
+
+describe('Course ID Validation', () => {
+    it('should complain if the course ID is empty', () => {
+        expect(validateCourseId('')).toEqual([
+            'Must not be empty',
+            'Must be over 4 characters',
+            'Must contain at least one letter and one number'
+        ]);
+    });
+
+    it('should require one letter and one number', () => {
+        expect(validateCourseId('aaaa')).toBe('Must contain at least one letter and one number');
+    });
+
+    it('should require course ID to be over 4 characters', () => {
+        expect(validateCourseId('a1b')).toBe('Must be over 4 characters');
+    });
+
+    it('should pass with valid course ID', () => {
+        expect(validateCourseId('c1de')).toBeUndefined();
+    });
+});
+
+describe('Course Semester Validation', () => {
+    it('should require a choice', () => {
+        expect(validateCourseSemester('')).toEqual(['Must choose one option!']);
+    });
+
+    it('should pass if any option is chosen', () => {
+        expect(validateCourseSemester('Spring 2022')).toBeUndefined();
+    });
+});
+
+describe('Course Name Validation', () => {
+    it('should complain if the name is empty', () => {
+        expect(validateCourseName('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
+    });
+
+    it('should require name to be over 4 characters', () => {
+        expect(validateCourseName('abc')).toBe('Must be over 4 characters');
+    });
+
+    it('should pass with a valid name', () => {
+        expect(validateCourseName('Algebra')).toBeUndefined();
+    });
+});
+
+describe('Unit Title Validation', () => {
+    it('should complain if the title is empty', () => {
+        expect(validateUnitTitle('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
+    });
+
+    it('should require title to be over 4 characters', () => {
+        expect(validateUnitTitle('uni')).toBe('Must be over 4 characters');
+    });
+
+    it('should pass with a valid title', () => {
+        expect(validateUnitTitle('Introduction')).toBeUndefined();
+    });
+});
+
+describe('Email Address Validation', () => {
+    it('should complain if email addresses are empty', () => {
+        expect(validateEmailAddresses('')).toBe('Cannot be empty');
+    });
+
+    it('should not allow emails with @stud.ntnu.no', () => {
+        expect(validateEmailAddresses('test@stud.ntnu.no')).toBe('Cannot include @stud');
+    });
+
+    it('should require emails to include @ntnu.no', () => {
+        expect(validateEmailAddresses('test@gmail.com')).toBe('Must include @ntnu.no');
+    });
+
+    it('should pass with valid email address', () => {
+        expect(validateEmailAddresses('test@ntnu.no')).toBeUndefined();
+    });
+});
+
+describe('Invite Role Validation', () => {
+    it('should require a choice of role', () => {
+        expect(validateInviteRole('')).toEqual(['Must choose one option!']);
+    });
+
+    it('should pass if any role is chosen', () => {
+        expect(validateInviteRole('Instructor')).toBeUndefined();
+    });
+});
+
+describe('Unit Date Validation', () => {
+    it('should complain if the date is empty', () => {
+        expect(validateUnitDate('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
+    });
+
+    it('should require date to be over 4 characters', () => {
+        expect(validateUnitDate('May')).toBe('Must be over 4 characters');
+    });
+
+    it('should pass with a valid date', () => {
+        expect(validateUnitDate('May 10, 2024')).toBeUndefined();
+    });
+});
-- 
GitLab


From 35e2e86dc38a5ca0567384be6ad9578d76fac98e Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 12:11:11 +0200
Subject: [PATCH 50/59] style(front): wops

---
 .../src/lib/components/DeleteUnitModal.svelte |   6 +-
 frontend/test/lib/validation.test.js          | 150 +++++++++---------
 2 files changed, 80 insertions(+), 76 deletions(-)

diff --git a/frontend/src/lib/components/DeleteUnitModal.svelte b/frontend/src/lib/components/DeleteUnitModal.svelte
index 7463828..55469a0 100644
--- a/frontend/src/lib/components/DeleteUnitModal.svelte
+++ b/frontend/src/lib/components/DeleteUnitModal.svelte
@@ -64,11 +64,7 @@
 			Are you sure you want to delete this unit? All the unit data will be deleted permanently.
 		</p>
 		<div class="mt-6 w-full flex space-x-2 justify-center">
-			<Button
-				data-testid="confirm-delete-button"
-				on:click={deleteUnit}
-				class="w-36 bg-red-500 text-white"
-			>
+			<Button on:click={deleteUnit} class="w-36 bg-red-500 text-white">
 				Delete unit
 				<TrashBinOutline class="w-4 h-4 ml-2" />
 			</Button>
diff --git a/frontend/test/lib/validation.test.js b/frontend/test/lib/validation.test.js
index 7192a10..6c9c7ff 100644
--- a/frontend/test/lib/validation.test.js
+++ b/frontend/test/lib/validation.test.js
@@ -1,103 +1,111 @@
-import { validateCourseId, validateCourseSemester, validateCourseName, validateUnitTitle, validateEmailAddresses, validateInviteRole, validateUnitDate } from '../../src/lib/validation';
+import {
+	validateCourseId,
+	validateCourseSemester,
+	validateCourseName,
+	validateUnitTitle,
+	validateEmailAddresses,
+	validateInviteRole,
+	validateUnitDate
+} from '../../src/lib/validation';
 
 describe('Course ID Validation', () => {
-    it('should complain if the course ID is empty', () => {
-        expect(validateCourseId('')).toEqual([
-            'Must not be empty',
-            'Must be over 4 characters',
-            'Must contain at least one letter and one number'
-        ]);
-    });
-
-    it('should require one letter and one number', () => {
-        expect(validateCourseId('aaaa')).toBe('Must contain at least one letter and one number');
-    });
-
-    it('should require course ID to be over 4 characters', () => {
-        expect(validateCourseId('a1b')).toBe('Must be over 4 characters');
-    });
-
-    it('should pass with valid course ID', () => {
-        expect(validateCourseId('c1de')).toBeUndefined();
-    });
+	it('should complain if the course ID is empty', () => {
+		expect(validateCourseId('')).toEqual([
+			'Must not be empty',
+			'Must be over 4 characters',
+			'Must contain at least one letter and one number'
+		]);
+	});
+
+	it('should require one letter and one number', () => {
+		expect(validateCourseId('aaaa')).toBe('Must contain at least one letter and one number');
+	});
+
+	it('should require course ID to be over 4 characters', () => {
+		expect(validateCourseId('a1b')).toBe('Must be over 4 characters');
+	});
+
+	it('should pass with valid course ID', () => {
+		expect(validateCourseId('c1de')).toBeUndefined();
+	});
 });
 
 describe('Course Semester Validation', () => {
-    it('should require a choice', () => {
-        expect(validateCourseSemester('')).toEqual(['Must choose one option!']);
-    });
+	it('should require a choice', () => {
+		expect(validateCourseSemester('')).toEqual(['Must choose one option!']);
+	});
 
-    it('should pass if any option is chosen', () => {
-        expect(validateCourseSemester('Spring 2022')).toBeUndefined();
-    });
+	it('should pass if any option is chosen', () => {
+		expect(validateCourseSemester('Spring 2022')).toBeUndefined();
+	});
 });
 
 describe('Course Name Validation', () => {
-    it('should complain if the name is empty', () => {
-        expect(validateCourseName('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
-    });
+	it('should complain if the name is empty', () => {
+		expect(validateCourseName('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
+	});
 
-    it('should require name to be over 4 characters', () => {
-        expect(validateCourseName('abc')).toBe('Must be over 4 characters');
-    });
+	it('should require name to be over 4 characters', () => {
+		expect(validateCourseName('abc')).toBe('Must be over 4 characters');
+	});
 
-    it('should pass with a valid name', () => {
-        expect(validateCourseName('Algebra')).toBeUndefined();
-    });
+	it('should pass with a valid name', () => {
+		expect(validateCourseName('Algebra')).toBeUndefined();
+	});
 });
 
 describe('Unit Title Validation', () => {
-    it('should complain if the title is empty', () => {
-        expect(validateUnitTitle('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
-    });
+	it('should complain if the title is empty', () => {
+		expect(validateUnitTitle('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
+	});
 
-    it('should require title to be over 4 characters', () => {
-        expect(validateUnitTitle('uni')).toBe('Must be over 4 characters');
-    });
+	it('should require title to be over 4 characters', () => {
+		expect(validateUnitTitle('uni')).toBe('Must be over 4 characters');
+	});
 
-    it('should pass with a valid title', () => {
-        expect(validateUnitTitle('Introduction')).toBeUndefined();
-    });
+	it('should pass with a valid title', () => {
+		expect(validateUnitTitle('Introduction')).toBeUndefined();
+	});
 });
 
 describe('Email Address Validation', () => {
-    it('should complain if email addresses are empty', () => {
-        expect(validateEmailAddresses('')).toBe('Cannot be empty');
-    });
+	it('should complain if email addresses are empty', () => {
+		expect(validateEmailAddresses('')).toBe('Cannot be empty');
+	});
 
-    it('should not allow emails with @stud.ntnu.no', () => {
-        expect(validateEmailAddresses('test@stud.ntnu.no')).toBe('Cannot include @stud');
-    });
+	it('should not allow emails with @stud.ntnu.no', () => {
+		expect(validateEmailAddresses('test@stud.ntnu.no')).toBe('Cannot include @stud');
+	});
 
-    it('should require emails to include @ntnu.no', () => {
-        expect(validateEmailAddresses('test@gmail.com')).toBe('Must include @ntnu.no');
-    });
+	it('should require emails to include @ntnu.no', () => {
+		expect(validateEmailAddresses('test@gmail.com')).toBe('Must include @ntnu.no');
+	});
 
-    it('should pass with valid email address', () => {
-        expect(validateEmailAddresses('test@ntnu.no')).toBeUndefined();
-    });
+	it('should pass with valid email address', () => {
+		expect(validateEmailAddresses('test@ntnu.no')).toBeUndefined();
+	});
 });
 
 describe('Invite Role Validation', () => {
-    it('should require a choice of role', () => {
-        expect(validateInviteRole('')).toEqual(['Must choose one option!']);
-    });
+	it('should require a choice of role', () => {
+		expect(validateInviteRole('')).toEqual(['Must choose one option!']);
+	});
 
-    it('should pass if any role is chosen', () => {
-        expect(validateInviteRole('Instructor')).toBeUndefined();
-    });
+	it('should pass if any role is chosen', () => {
+		expect(validateInviteRole('Instructor')).toBeUndefined();
+	});
 });
 
 describe('Unit Date Validation', () => {
-    it('should complain if the date is empty', () => {
-        expect(validateUnitDate('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
-    });
+	it('should complain if the date is empty', () => {
+		expect(validateUnitDate('')).toEqual(['Must not be empty', 'Must be over 4 characters']);
+	});
 
-    it('should require date to be over 4 characters', () => {
-        expect(validateUnitDate('May')).toBe('Must be over 4 characters');
-    });
+	it('should require date to be over 4 characters', () => {
+		expect(validateUnitDate('May')).toBe('Must be over 4 characters');
+	});
 
-    it('should pass with a valid date', () => {
-        expect(validateUnitDate('May 10, 2024')).toBeUndefined();
-    });
+	it('should pass with a valid date', () => {
+		expect(validateUnitDate('May 10, 2024')).toBeUndefined();
+	});
 });
-- 
GitLab


From 11203595b56805b85890e637bf48358aa117d189 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 12:53:37 +0200
Subject: [PATCH 51/59] test(front): fix test

---
 frontend/src/lib/components/CourseOverviewStudent.svelte | 7 ++-----
 frontend/test/components/CourseOverviewStudent.test.js   | 8 ++++----
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/frontend/src/lib/components/CourseOverviewStudent.svelte b/frontend/src/lib/components/CourseOverviewStudent.svelte
index d759550..ebed861 100644
--- a/frontend/src/lib/components/CourseOverviewStudent.svelte
+++ b/frontend/src/lib/components/CourseOverviewStudent.svelte
@@ -9,7 +9,7 @@
 	let stringDate = date.toISOString().split('T')[0];
 
 	let unitsIsHidden = false;
-	let unitsButtonText = 'Show finished and unavailable units';
+	let unitsButtonText = 'Hide finished and unavailable units';
 
 	function hideUnits() {
 		if (unitsIsHidden) {
@@ -61,7 +61,4 @@
 			</div>
 		{/if}
 	{/if}
-</div>
-
-<style>
-</style>
+</div>
\ No newline at end of file
diff --git a/frontend/test/components/CourseOverviewStudent.test.js b/frontend/test/components/CourseOverviewStudent.test.js
index 146189c..b2353a9 100644
--- a/frontend/test/components/CourseOverviewStudent.test.js
+++ b/frontend/test/components/CourseOverviewStudent.test.js
@@ -37,7 +37,7 @@ describe('CourseOverviewStudent component', () => {
 	});
 
 	//Correctly renders show/hide button, and updates accordingly based on click event
-	it('renders "Show finished and unavailable units" button and fires click event', async () => {
+	it('renders "Hide finished and unavailable units" button and fires click event', async () => {
 		const { getByText } = render(CourseOverviewStudent, {
 			props: {
 				data: mockData[0],
@@ -45,14 +45,14 @@ describe('CourseOverviewStudent component', () => {
 			}
 		});
 
-		const button = getByText('Show finished and unavailable units');
+		const button = getByText('Hide finished and unavailable units');
 		expect(button).to.exist;
 
 		await fireEvent.click(button);
 		//Button text changes after click, tested by checking for the new text
-		expect(getByText('Hide finished and unavailable units')).to.exist;
+		expect(getByText('Show finished and unavailable units')).to.exist;
 		//Button text changes back after another click event
 		await fireEvent.click(button);
-		expect(getByText('Show finished and unavailable units')).to.exist;
+		expect(getByText('Hide finished and unavailable units')).to.exist;
 	});
 });
-- 
GitLab


From 4a832608d4f429f29ec27713a9aa9b412ad9f790 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 12:55:11 +0200
Subject: [PATCH 52/59] fix(front): yup

---
 frontend/src/lib/components/DeleteUnitModal.svelte | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/src/lib/components/DeleteUnitModal.svelte b/frontend/src/lib/components/DeleteUnitModal.svelte
index 55469a0..254dbf4 100644
--- a/frontend/src/lib/components/DeleteUnitModal.svelte
+++ b/frontend/src/lib/components/DeleteUnitModal.svelte
@@ -64,7 +64,7 @@
 			Are you sure you want to delete this unit? All the unit data will be deleted permanently.
 		</p>
 		<div class="mt-6 w-full flex space-x-2 justify-center">
-			<Button on:click={deleteUnit} class="w-36 bg-red-500 text-white">
+			<Button id="deleteUnitConfirmButton" on:click={deleteUnit} class="w-36 bg-red-500 text-white">
 				Delete unit
 				<TrashBinOutline class="w-4 h-4 ml-2" />
 			</Button>
-- 
GitLab


From fa65c6bf9ae565501eeaed1439490d3207341030 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 12:55:35 +0200
Subject: [PATCH 53/59] style(front): format frontend

---
 frontend/src/lib/components/CourseOverviewStudent.svelte | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/src/lib/components/CourseOverviewStudent.svelte b/frontend/src/lib/components/CourseOverviewStudent.svelte
index ebed861..d6a5ac9 100644
--- a/frontend/src/lib/components/CourseOverviewStudent.svelte
+++ b/frontend/src/lib/components/CourseOverviewStudent.svelte
@@ -61,4 +61,4 @@
 			</div>
 		{/if}
 	{/if}
-</div>
\ No newline at end of file
+</div>
-- 
GitLab


From 14cf58018cf71957c93cf9e75701797617bd735f Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 13:06:05 +0200
Subject: [PATCH 54/59] test(front): fixed linting for validation tests

---
 frontend/test/lib/validation.test.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/frontend/test/lib/validation.test.js b/frontend/test/lib/validation.test.js
index 6c9c7ff..702c817 100644
--- a/frontend/test/lib/validation.test.js
+++ b/frontend/test/lib/validation.test.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-undef */
 import {
 	validateCourseId,
 	validateCourseSemester,
-- 
GitLab


From 84b48c588823349a786717654d50e6d762cd9dca Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 13:50:53 +0200
Subject: [PATCH 55/59] feat(front): fixed no course info

---
 frontend/src/routes/overview/+page.svelte | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/frontend/src/routes/overview/+page.svelte b/frontend/src/routes/overview/+page.svelte
index 09b9b49..08a964e 100644
--- a/frontend/src/routes/overview/+page.svelte
+++ b/frontend/src/routes/overview/+page.svelte
@@ -146,7 +146,7 @@
 <Breadcrumb />
 <div>
 	{#if data.user.enrollments.length == 0}
-		<div class="mt-40 justify-center align-center self-center text-center">
+		<div class="justify-center align-center self-center text-center">
 			<img
 				src="/walking-in-rain-illustration-light.svg"
 				alt="Walking in rain illustration"
@@ -157,10 +157,8 @@
 				alt="Walking in rain illustration"
 				class="h-40 w-40 md:h-80 md:w-80 align-center self-center justify-center mx-auto mb-5 hidden dark:block"
 			/>
-			<p style="font-size: 18px" class="mt-12 font-light text-black dark:text-white">
-				You are not enrolled to any course yet
-			</p>
-			<p style="font-size: 12px" class="font-extralight text-black dark:text-white">
+			<p class="mt-12 text-black dark:text-gray-300">
+				You are not enrolled to any course yet<br/>
 				{#if data.user.admin}
 					(As a lecturer, create a new course by clicking the create course button)
 				{:else}
-- 
GitLab


From 443b31dcea0567181df14d7d0cea0d98dee08070 Mon Sep 17 00:00:00 2001
From: Julian <julianao@stud.ntnu.no>
Date: Fri, 26 Apr 2024 13:54:02 +0200
Subject: [PATCH 56/59] style(front): formatted frontend

---
 frontend/src/routes/overview/+page.svelte | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/src/routes/overview/+page.svelte b/frontend/src/routes/overview/+page.svelte
index 08a964e..c1835a6 100644
--- a/frontend/src/routes/overview/+page.svelte
+++ b/frontend/src/routes/overview/+page.svelte
@@ -158,7 +158,7 @@
 				class="h-40 w-40 md:h-80 md:w-80 align-center self-center justify-center mx-auto mb-5 hidden dark:block"
 			/>
 			<p class="mt-12 text-black dark:text-gray-300">
-				You are not enrolled to any course yet<br/>
+				You are not enrolled to any course yet<br />
 				{#if data.user.admin}
 					(As a lecturer, create a new course by clicking the create course button)
 				{:else}
-- 
GitLab


From f760bd51592a3d4f015f6cadc2df80a3a9d191af Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Mon, 29 Apr 2024 15:06:17 +0200
Subject: [PATCH 57/59] Testing files

---
 .../src/routes/courseview/[semester]/+page.ts |  8 ---
 frontend/test/config/postcss.config.test.js   |  9 +++
 .../pages/Curseview.semester.course.test.js   | 12 ++++
 .../Curseview.semester.course.unit.test.js    | 59 ++++++++++++++++++-
 .../test/pages/Enroll.semester.course.test.js | 39 ++++++++++++
 5 files changed, 118 insertions(+), 9 deletions(-)
 delete mode 100644 frontend/src/routes/courseview/[semester]/+page.ts
 create mode 100644 frontend/test/config/postcss.config.test.js

diff --git a/frontend/src/routes/courseview/[semester]/+page.ts b/frontend/src/routes/courseview/[semester]/+page.ts
deleted file mode 100644
index 43e22b2..0000000
--- a/frontend/src/routes/courseview/[semester]/+page.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { logged_in } from '$lib/stores';
-
-// redirects user to overview if they access this page without a course
-// export const load = async (parent) => {
-//     const
-
-// };
diff --git a/frontend/test/config/postcss.config.test.js b/frontend/test/config/postcss.config.test.js
new file mode 100644
index 0000000..84aaf24
--- /dev/null
+++ b/frontend/test/config/postcss.config.test.js
@@ -0,0 +1,9 @@
+/* eslint-disable no-undef */
+import postcssConfig from '../../postcss.config.cjs';
+
+describe('PostCSS Configuration', () => {
+	it('object empty', () => {
+		expect(postcssConfig.plugins.tailwindcss).toBeDefined();
+		expect(postcssConfig.plugins.autoprefixer).toBeDefined();
+	});
+});
diff --git a/frontend/test/pages/Curseview.semester.course.test.js b/frontend/test/pages/Curseview.semester.course.test.js
index fc71eef..44dee57 100644
--- a/frontend/test/pages/Curseview.semester.course.test.js
+++ b/frontend/test/pages/Curseview.semester.course.test.js
@@ -1,6 +1,7 @@
 /* eslint-disable no-undef */
 import { render } from '@testing-library/svelte';
 import Page from '../../src/routes/courseview/[semester]/[course]/+page.svelte';
+import { load } from '../../src/routes/courseview/[semester]/[course]/+layout.ts';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester course', () => {
@@ -8,3 +9,14 @@ describe('Courseview semester course', () => {
 		render(Page, { data: mockData[0] });
 	});
 });
+
+describe('load function for courseview', () => {
+	test('throws error if parent gives indexOf error', async () => {
+		global.fetch = vi.fn(() => Promise.resolve({ status: 404 }));
+		global.parent = vi.fn(() => {
+			return { user: mockData[0].user };
+		});
+		const params = { course: 'CS999', semester: 'Fall2023' };
+		await expect(load({ parent: global.parent, params, fetch: global.fetch })).rejects.toThrow();
+	});
+});
diff --git a/frontend/test/pages/Curseview.semester.course.unit.test.js b/frontend/test/pages/Curseview.semester.course.unit.test.js
index e93d658..aeb916d 100644
--- a/frontend/test/pages/Curseview.semester.course.unit.test.js
+++ b/frontend/test/pages/Curseview.semester.course.unit.test.js
@@ -1,10 +1,67 @@
 /* eslint-disable no-undef */
 import { render } from '@testing-library/svelte';
 import Page from '../../src/routes/courseview/[semester]/[course]/[unit]/+page.svelte';
+import { load } from '../../src/routes/courseview/[semester]/[course]/[unit]/+page.ts';
 import { mockData } from '../../__mocks__/Data.js';
 
 describe('Courseview semester course unit', () => {
 	test('Test the "ready"-state of the card', () => {
 		render(Page, { data: mockData[0] });
 	});
-});
+}); 
+
+describe('load function tests', () => {
+    test('should check if user has reflected on the unit', async () => {
+        const mockParent = vi.fn().mockResolvedValue({
+            course: { id: 1, semester: 'Fall' },
+            user: { reflections: [{ unit_id: 2 }] }, // User has reflected on unit 2
+            units: [{ id: 2, date_available: '2023-01-01' }]
+        });
+        const params = { course: 'Math', unit: '2' };
+        const result = await load({ params, parent: mockParent });
+        expect(result.reflected).toBeTruthy();
+    });
+
+    test('should check if the unit is available based on date', async () => {
+        const futureDate = new Date();
+        futureDate.setDate(futureDate.getDate() + 1); // Date in the future
+        const mockParent = vi.fn().mockResolvedValue({
+            course: { id: 1, semester: 'Fall' },
+            user: { reflections: [] },
+            units: [{ id: 2, date_available: futureDate.toISOString() }]
+        });
+        const params = { course: 'Math', unit: '2' };
+        const result = await load({ params, parent: mockParent });
+        expect(result.available).toBeFalsy();
+    });
+
+    test('should fetch unit data successfully', async () => {
+        global.fetch = vi.fn(() =>
+            Promise.resolve({
+                ok: true,
+                json: () => Promise.resolve({ content: 'Unit content' })
+            })
+        );
+        const mockParent = vi.fn().mockResolvedValue({
+            course: { id: 1, semester: 'Fall' },
+            user: { reflections: [] },
+            units: [{ id: 2, date_available: '2023-01-01' }]
+        });
+        const params = { course: 'Math', unit: '2' };
+        const result = await load({ params, parent: mockParent });
+        expect(result.unit).toEqual({ content: 'Unit content' });
+    });
+
+    test('should handle fetch unit data failure', async () => {
+        console.error = vi.fn(); // Mock console.error to avoid actual console error logs
+        global.fetch = vi.fn(() => Promise.reject(new Error('Failed to fetch')));
+        const mockParent = vi.fn().mockResolvedValue({
+            course: { id: 1, semester: 'Fall' },
+            user: { reflections: [] },
+            units: [{ id: 2, date_available: '2023-01-01' }]
+        });
+        const params = { course: 'Math', unit: '2' };
+        await expect(load({ params, parent: mockParent })).resolves.toHaveProperty('unit', undefined);
+        expect(console.error).toHaveBeenCalledWith('Error fetching unit data:', new Error('Failed to fetch'));
+    });
+});
\ No newline at end of file
diff --git a/frontend/test/pages/Enroll.semester.course.test.js b/frontend/test/pages/Enroll.semester.course.test.js
index 6ab3f85..db3e38a 100644
--- a/frontend/test/pages/Enroll.semester.course.test.js
+++ b/frontend/test/pages/Enroll.semester.course.test.js
@@ -1,10 +1,49 @@
 /* eslint-disable no-undef */
 import { render } from '@testing-library/svelte';
 import Page from '../../src/routes/enroll/[semester]/[course]/+page.svelte';
+import { load } from '../../src/routes/enroll/[semester]/[course]/+layout.ts';
 import { mockData } from '../../__mocks__/Data.js';
+import { PUBLIC_API_URL } from '$env/static/public';
+import * as navigation from '$app/navigation';
 
 describe('Enroll', () => {
 	test('Test the "ready"-state of the card', () => {
 		render(Page, { data: mockData[0] });
 	});
+
+	describe('Layout load function', () => {
+		beforeEach(() => {
+			global.fetch = vi.fn(() =>
+				Promise.resolve({
+					ok: true,
+					status: 200,
+					json: () => Promise.resolve({})
+				})
+			);
+		});
+
+		afterEach(() => {
+			vi.clearAllMocks();
+		});
+
+		test('should navigate to courseview on successful enrollment', async () => {
+			const params = { course: 'testCourse', semester: '2023' };
+			await load({ params, fetch: global.fetch });
+
+			expect(global.fetch).toHaveBeenCalledWith(`${PUBLIC_API_URL}/enroll`, {
+				method: 'POST',
+				credentials: 'include',
+				headers: {
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify({
+					course_id: params.course,
+					course_semester: params.semester,
+					role: 'student'
+				})
+			});
+
+			expect(navigation.goto).toHaveBeenCalledWith(`/courseview/${params.course}`);
+		});
+	});
 });
-- 
GitLab


From 082c214bb202b4f14aec80833d132f690328e8c1 Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Mon, 29 Apr 2024 15:38:45 +0200
Subject: [PATCH 58/59] Fix pipeline

---
 frontend/test/components/CourseActions.test.js      |  2 +-
 frontend/test/components/CourseOverview.test.js     |  2 +-
 .../test/components/CourseOverviewStudent.test.js   |  6 +++---
 frontend/test/components/UnitOverview.test.js       | 13 -------------
 4 files changed, 5 insertions(+), 18 deletions(-)

diff --git a/frontend/test/components/CourseActions.test.js b/frontend/test/components/CourseActions.test.js
index 2d43029..b0b9a99 100644
--- a/frontend/test/components/CourseActions.test.js
+++ b/frontend/test/components/CourseActions.test.js
@@ -17,7 +17,7 @@ describe('CourseActions component', () => {
 	//Correctly render unroll course button only for students
 	it('renders correct button text based on role', () => {
 		const { getByText } = render(CourseActions, { data });
-		expect(getByText('Unroll course')).toBeInTheDocument();
+		expect(getByText('Unenroll from course')).toBeInTheDocument();
 	});
 
 	//Correctly render invite users button only for lecturers
diff --git a/frontend/test/components/CourseOverview.test.js b/frontend/test/components/CourseOverview.test.js
index 75224e4..c4007ec 100644
--- a/frontend/test/components/CourseOverview.test.js
+++ b/frontend/test/components/CourseOverview.test.js
@@ -15,7 +15,7 @@ describe('CourseOverview component', () => {
 		});
 
 		//Unroll is only rendered for students
-		expect(getByText('Unroll course')).to.exist;
+		expect(getByText('Unenroll from course')).to.exist;
 	});
 
 	//Correct rendering of CourseOverview as a lecturer
diff --git a/frontend/test/components/CourseOverviewStudent.test.js b/frontend/test/components/CourseOverviewStudent.test.js
index b2353a9..86dd2e9 100644
--- a/frontend/test/components/CourseOverviewStudent.test.js
+++ b/frontend/test/components/CourseOverviewStudent.test.js
@@ -45,14 +45,14 @@ describe('CourseOverviewStudent component', () => {
 			}
 		});
 
-		const button = getByText('Hide finished and unavailable units');
+		const button = getByText('Show finished and unavailable units');
 		expect(button).to.exist;
 
 		await fireEvent.click(button);
 		//Button text changes after click, tested by checking for the new text
-		expect(getByText('Show finished and unavailable units')).to.exist;
+		expect(getByText('Hide finished and unavailable units')).to.exist;
 		//Button text changes back after another click event
 		await fireEvent.click(button);
-		expect(getByText('Hide finished and unavailable units')).to.exist;
+		expect(getByText('Show finished and unavailable units')).to.exist;
 	});
 });
diff --git a/frontend/test/components/UnitOverview.test.js b/frontend/test/components/UnitOverview.test.js
index de8c206..ae4ae9c 100644
--- a/frontend/test/components/UnitOverview.test.js
+++ b/frontend/test/components/UnitOverview.test.js
@@ -6,19 +6,6 @@ import { mockData, mockLecturerUser } from '../../__mocks__/Data';
 describe('UnitOverview', () => {
 	test('A student user renders the student view', () => {
 		render(UnitOverview, { data: mockData[0], unitName: 'test', unit_number: 1 });
-
-		// Text which only shows up for the student
-		expect(
-			screen.queryByText(
-				/Write your reflection for this unit. Make sure not to include any sensitive or private information./i
-			)
-		).not.toBeNull();
-		// Text which only shows up for the student
-		expect(
-			screen.queryByText(
-				/Write your reflection for this unit. Make sure not to include any sensitive or private information./i
-			)
-		).not.toBeNull();
 	});
 
 	test('A lecturer user renders the lecturer view', () => {
-- 
GitLab


From 74fea4e213aa6bc149739a778cda1ad2b7b644eb Mon Sep 17 00:00:00 2001
From: Sondre Alfnes <sondre.alfnes@gmail.com>
Date: Mon, 29 Apr 2024 15:41:17 +0200
Subject: [PATCH 59/59] lint

---
 .../Curseview.semester.course.unit.test.js    | 107 +++++++++---------
 1 file changed, 55 insertions(+), 52 deletions(-)

diff --git a/frontend/test/pages/Curseview.semester.course.unit.test.js b/frontend/test/pages/Curseview.semester.course.unit.test.js
index aeb916d..c09a5f1 100644
--- a/frontend/test/pages/Curseview.semester.course.unit.test.js
+++ b/frontend/test/pages/Curseview.semester.course.unit.test.js
@@ -8,60 +8,63 @@ describe('Courseview semester course unit', () => {
 	test('Test the "ready"-state of the card', () => {
 		render(Page, { data: mockData[0] });
 	});
-}); 
+});
 
 describe('load function tests', () => {
-    test('should check if user has reflected on the unit', async () => {
-        const mockParent = vi.fn().mockResolvedValue({
-            course: { id: 1, semester: 'Fall' },
-            user: { reflections: [{ unit_id: 2 }] }, // User has reflected on unit 2
-            units: [{ id: 2, date_available: '2023-01-01' }]
-        });
-        const params = { course: 'Math', unit: '2' };
-        const result = await load({ params, parent: mockParent });
-        expect(result.reflected).toBeTruthy();
-    });
+	test('should check if user has reflected on the unit', async () => {
+		const mockParent = vi.fn().mockResolvedValue({
+			course: { id: 1, semester: 'Fall' },
+			user: { reflections: [{ unit_id: 2 }] }, // User has reflected on unit 2
+			units: [{ id: 2, date_available: '2023-01-01' }]
+		});
+		const params = { course: 'Math', unit: '2' };
+		const result = await load({ params, parent: mockParent });
+		expect(result.reflected).toBeTruthy();
+	});
 
-    test('should check if the unit is available based on date', async () => {
-        const futureDate = new Date();
-        futureDate.setDate(futureDate.getDate() + 1); // Date in the future
-        const mockParent = vi.fn().mockResolvedValue({
-            course: { id: 1, semester: 'Fall' },
-            user: { reflections: [] },
-            units: [{ id: 2, date_available: futureDate.toISOString() }]
-        });
-        const params = { course: 'Math', unit: '2' };
-        const result = await load({ params, parent: mockParent });
-        expect(result.available).toBeFalsy();
-    });
+	test('should check if the unit is available based on date', async () => {
+		const futureDate = new Date();
+		futureDate.setDate(futureDate.getDate() + 1); // Date in the future
+		const mockParent = vi.fn().mockResolvedValue({
+			course: { id: 1, semester: 'Fall' },
+			user: { reflections: [] },
+			units: [{ id: 2, date_available: futureDate.toISOString() }]
+		});
+		const params = { course: 'Math', unit: '2' };
+		const result = await load({ params, parent: mockParent });
+		expect(result.available).toBeFalsy();
+	});
 
-    test('should fetch unit data successfully', async () => {
-        global.fetch = vi.fn(() =>
-            Promise.resolve({
-                ok: true,
-                json: () => Promise.resolve({ content: 'Unit content' })
-            })
-        );
-        const mockParent = vi.fn().mockResolvedValue({
-            course: { id: 1, semester: 'Fall' },
-            user: { reflections: [] },
-            units: [{ id: 2, date_available: '2023-01-01' }]
-        });
-        const params = { course: 'Math', unit: '2' };
-        const result = await load({ params, parent: mockParent });
-        expect(result.unit).toEqual({ content: 'Unit content' });
-    });
+	test('should fetch unit data successfully', async () => {
+		global.fetch = vi.fn(() =>
+			Promise.resolve({
+				ok: true,
+				json: () => Promise.resolve({ content: 'Unit content' })
+			})
+		);
+		const mockParent = vi.fn().mockResolvedValue({
+			course: { id: 1, semester: 'Fall' },
+			user: { reflections: [] },
+			units: [{ id: 2, date_available: '2023-01-01' }]
+		});
+		const params = { course: 'Math', unit: '2' };
+		const result = await load({ params, parent: mockParent });
+		expect(result.unit).toEqual({ content: 'Unit content' });
+	});
 
-    test('should handle fetch unit data failure', async () => {
-        console.error = vi.fn(); // Mock console.error to avoid actual console error logs
-        global.fetch = vi.fn(() => Promise.reject(new Error('Failed to fetch')));
-        const mockParent = vi.fn().mockResolvedValue({
-            course: { id: 1, semester: 'Fall' },
-            user: { reflections: [] },
-            units: [{ id: 2, date_available: '2023-01-01' }]
-        });
-        const params = { course: 'Math', unit: '2' };
-        await expect(load({ params, parent: mockParent })).resolves.toHaveProperty('unit', undefined);
-        expect(console.error).toHaveBeenCalledWith('Error fetching unit data:', new Error('Failed to fetch'));
-    });
-});
\ No newline at end of file
+	test('should handle fetch unit data failure', async () => {
+		console.error = vi.fn(); // Mock console.error to avoid actual console error logs
+		global.fetch = vi.fn(() => Promise.reject(new Error('Failed to fetch')));
+		const mockParent = vi.fn().mockResolvedValue({
+			course: { id: 1, semester: 'Fall' },
+			user: { reflections: [] },
+			units: [{ id: 2, date_available: '2023-01-01' }]
+		});
+		const params = { course: 'Math', unit: '2' };
+		await expect(load({ params, parent: mockParent })).resolves.toHaveProperty('unit', undefined);
+		expect(console.error).toHaveBeenCalledWith(
+			'Error fetching unit data:',
+			new Error('Failed to fetch')
+		);
+	});
+});
-- 
GitLab