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 @@ - 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,