diff --git a/web/package-lock.json b/web/package-lock.json
index 9e5a55d5..dc2950f1 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -35,6 +35,7 @@
"vuetify": "^3.7.5"
},
"devDependencies": {
+ "@faker-js/faker": "^9.3.0",
"@mdi/font": "^7.4.47",
"@mdi/js": "^7.4.47",
"@typescript-eslint/eslint-plugin": "^8.17.0",
@@ -658,6 +659,23 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@faker-js/faker": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.3.0.tgz",
+ "integrity": "sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fakerjs"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=9.0.0"
+ }
+ },
"node_modules/@fullcalendar/core": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
diff --git a/web/package.json b/web/package.json
index 48e81124..41c36fa5 100644
--- a/web/package.json
+++ b/web/package.json
@@ -48,6 +48,7 @@
"vuetify": "^3.7.5"
},
"devDependencies": {
+ "@faker-js/faker": "^9.3.0",
"@mdi/font": "^7.4.47",
"@mdi/js": "^7.4.47",
"@typescript-eslint/eslint-plugin": "^8.17.0",
diff --git a/web/src/App.vue b/web/src/App.vue
index 5c19aa06..35f78f7f 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -1,27 +1,44 @@
-
-
-
- JASPER
-
-
- Dashboard
- Court list
- Court file search
-
-
-
-
-
-
+
+
+
+
+
+ JASPER
+
+
+ Dashboard
+ Court list
+ Court file search
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/web/src/stores/ThemeStore.ts b/web/src/stores/ThemeStore.ts
new file mode 100644
index 00000000..65227360
--- /dev/null
+++ b/web/src/stores/ThemeStore.ts
@@ -0,0 +1,14 @@
+import { ref } from 'vue';
+
+// We want the default experience to be light mode
+const theme = localStorage.getItem('theme') ?? 'light';
+const state = ref(theme);
+
+const changeState = (newTheme: string) => {
+ localStorage.setItem('theme', newTheme);
+ state.value = newTheme;
+};
+
+export const useThemeStore = () => {
+ return { state, changeState };
+};
diff --git a/web/tests/components/shared/ProfileOffCanvas.test.ts b/web/tests/components/shared/ProfileOffCanvas.test.ts
new file mode 100644
index 00000000..a688f5e3
--- /dev/null
+++ b/web/tests/components/shared/ProfileOffCanvas.test.ts
@@ -0,0 +1,39 @@
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import ProfileOffCanvas from 'CMP/shared/ProfileOffCanvas.vue';
+
+vi.mock('SRC/stores/ThemeStore', () => ({
+ useThemeStore: () => ({
+ theme: 'light',
+ setTheme: vi.fn(),
+ changeState: vi.fn(),
+ }),
+}));
+
+describe('ProfileOffCanvas.vue', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = mount(ProfileOffCanvas);
+ });
+
+ it('renders the component', () => {
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ // Unable to dive deeper into slotted append/prepend components
+ // todo: find a way to test the slotted components
+ // it('calls close when close button clicked', async () => {
+ // await wrapper.find('v-button').trigger('click');
+
+ // expect(wrapper.emitted()).toHaveProperty('close');
+ // });
+
+ // it('calls set theme to dark when toggle button is clicked', async () => {
+ // await wrapper.find('v-switch').trigger('click');
+
+ // expect(themeStore.changeState).toHaveBeenCalledWith('dark');
+ // });
+});
+
+
diff --git a/web/tests/stores/ThemeStore.test.ts b/web/tests/stores/ThemeStore.test.ts
new file mode 100644
index 00000000..decc1bca
--- /dev/null
+++ b/web/tests/stores/ThemeStore.test.ts
@@ -0,0 +1,30 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { useThemeStore } from '@/stores/ThemeStore';
+
+describe('ThemeStore', () => {
+ let themeStore: ReturnType;
+
+ beforeEach(() => {
+ themeStore = useThemeStore();
+ });
+
+ afterEach(() => {
+ localStorage.clear();
+ });
+
+ it('should initialize with light theme by default', () => {
+ expect(themeStore.state.value).toBe('light');
+ });
+
+ it('should change theme and update localStorage', () => {
+ themeStore.changeState('dark');
+ expect(themeStore.state.value).toBe('dark');
+ expect(localStorage.getItem('theme')).toBe('dark');
+ });
+
+ it('should persist theme from localStorage', () => {
+ localStorage.setItem('theme', 'dark');
+ themeStore = useThemeStore();
+ expect(themeStore.state.value).toBe('dark');
+ });
+});
\ No newline at end of file
diff --git a/web/vitest.config.js b/web/vitest.config.js
index 853ff3e2..d1f021e4 100644
--- a/web/vitest.config.js
+++ b/web/vitest.config.js
@@ -12,9 +12,13 @@ export default defineConfig({
},
test: {
alias: [
+ { find: '@', replacement: resolve(basePath, './src') },
{ find: 'SRC', replacement: resolve(basePath, './src') },
{ find: 'CMP', replacement: resolve(basePath, './src/components') }
],
+ deps: {
+ inline: ['vuetify']
+ },
css: true,
environment: 'happy-dom',
globals: true,