From 278b06d4b9e78b5bef148656b51d6a038d43f0fb Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Tue, 11 Jun 2024 20:48:26 +0200 Subject: [PATCH 1/8] Add error handling and toastify package - Added toastify package - Implemented a general error handler - Replaced all console.log with new error handler - Added test (currently not working) --- frontend/package-lock.json | 22 +++++++++++++++++ frontend/package.json | 1 + frontend/renderer/app.ts | 13 ++++++++++ .../renderer/plugins/GlobalErrorHandler.ts | 24 +++++++++++++++++++ frontend/src/components/menu/UserDropdown.vue | 4 ++-- frontend/src/pages/auth/+Page.vue | 4 ++-- frontend/src/pages/signin/+Page.vue | 4 ++-- frontend/src/pages/silent-refresh/+Page.vue | 4 ++-- 8 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 frontend/renderer/plugins/GlobalErrorHandler.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 97e2031a6..6d549e59f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -41,6 +41,7 @@ "vite-svg-loader": "^5.1.0", "vue": "3.4.27", "vue-i18n": "^9.13.1", + "vue3-toastify": "^0.2.1", "vuetify": "^3.5.17" }, "devDependencies": { @@ -29051,6 +29052,27 @@ "source-map-js": "^1.2.0" } }, + "node_modules/vue3-toastify": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vue3-toastify/-/vue3-toastify-0.2.1.tgz", + "integrity": "sha512-u4i5LCu1q5qs4L4Kbjb4u8NipCS8ox1fCHQ6XFS62676xnA6Q/AJRpZEkAurTMp723LeH6eQX6k9+24bKf1T4Q==", + "workspaces": [ + "docs", + "playground" + ], + "engines": { + "node": ">=16", + "npm": ">=7" + }, + "peerDependencies": { + "vue": ">=3.2.0" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, "node_modules/vuepress": { "version": "2.0.0-rc.13", "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-2.0.0-rc.13.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7140bdae4..8523f3b3e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -94,6 +94,7 @@ "vite-svg-loader": "^5.1.0", "vue": "3.4.27", "vue-i18n": "^9.13.1", + "vue3-toastify": "^0.2.1", "vuetify": "^3.5.17" }, "devDependencies": { diff --git a/frontend/renderer/app.ts b/frontend/renderer/app.ts index bdce728ef..228810b59 100644 --- a/frontend/renderer/app.ts +++ b/frontend/renderer/app.ts @@ -2,16 +2,21 @@ import { DefaultApolloClient } from '@vue/apollo-composable' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import { PageContext } from 'vike/types' import { createSSRApp, defineComponent, h, markRaw, reactive, Component, provide } from 'vue' +import Vue3Toasity from 'vue3-toastify' +import 'vue3-toastify/dist/index.css' import PageShell from '#components/PageShell.vue' import { setPageContext } from '#context/usePageContext' import { createApolloClient } from '#plugins/apollo' +import GlobalErrorHandler from '#plugins/GlobalErrorHandler' import i18n from '#plugins/i18n' import pinia from '#plugins/pinia' import CreateVuetify from '#plugins/vuetify' import AuthService from '#src/services/AuthService' import { useAuthStore } from '#stores/authStore' +import type { ToastContainerOptions } from 'vue3-toastify' + const vuetify = CreateVuetify(i18n) function createApp(pageContext: PageContext, isClient = true) { @@ -52,6 +57,14 @@ function createApp(pageContext: PageContext, isClient = true) { app.use(pinia) app.use(i18n) app.use(vuetify) + app.use(Vue3Toasity, { + autoClose: 3000, + style: { + opacity: '1', + userSelect: 'initial', + }, + } as ToastContainerOptions) + app.use(GlobalErrorHandler) const auth = useAuthStore() diff --git a/frontend/renderer/plugins/GlobalErrorHandler.ts b/frontend/renderer/plugins/GlobalErrorHandler.ts new file mode 100644 index 000000000..2cd9a2422 --- /dev/null +++ b/frontend/renderer/plugins/GlobalErrorHandler.ts @@ -0,0 +1,24 @@ +import { App } from 'vue' +import { toast } from 'vue3-toastify' + +const handleError = (message: string, data?: unknown) => { + // eslint-disable-next-line no-console + console.error('error: ' + message, data) + const id = toast.error(message, { delay: 0 }) + console.error(toast.isActive(id)) +} +const handleWarning = (message: string) => { + // eslint-disable-next-line no-console + console.log('warning: ' + message) + toast.warning(message) +} + +export default { + install: (app: App) => { + app.config.errorHandler = (error, vm, info) => { + handleError(`Unhandled error occurred: ${info}`, error) + } + }, + error: handleError, + warning: handleWarning, +} diff --git a/frontend/src/components/menu/UserDropdown.vue b/frontend/src/components/menu/UserDropdown.vue index 016a9245e..d03467c40 100644 --- a/frontend/src/components/menu/UserDropdown.vue +++ b/frontend/src/components/menu/UserDropdown.vue @@ -18,6 +18,7 @@ import { inject } from 'vue' import MainButton from '#components/buttons/MainButton.vue' +import GlobalErrorHandler from '#plugins/GlobalErrorHandler' import { AUTH } from '#src/env' import AuthService from '#src/services/AuthService' import { useAuthStore } from '#stores/authStore' @@ -30,8 +31,7 @@ async function signOut() { try { await authService?.signOut() } catch (error) { - // eslint-disable-next-line no-console - console.log('auth error', error) + GlobalErrorHandler.error('auth error', error) } } diff --git a/frontend/src/pages/auth/+Page.vue b/frontend/src/pages/auth/+Page.vue index 63810be70..179a2f8fc 100644 --- a/frontend/src/pages/auth/+Page.vue +++ b/frontend/src/pages/auth/+Page.vue @@ -9,6 +9,7 @@ import { navigate } from 'vike/client/router' import { inject, onBeforeMount } from 'vue' import DefaultLayout from '#layouts/DefaultLayout.vue' +import GlobalErrorHandler from '#plugins/GlobalErrorHandler' import AuthService from '#src/services/AuthService' const authService = inject('authService') @@ -21,8 +22,7 @@ onBeforeMount(async () => { } navigate('/') } catch (error) { - // eslint-disable-next-line no-console - console.log('auth error', error) + GlobalErrorHandler.error('auth error', error) } }) diff --git a/frontend/src/pages/signin/+Page.vue b/frontend/src/pages/signin/+Page.vue index d8f388ac6..ec2b18568 100644 --- a/frontend/src/pages/signin/+Page.vue +++ b/frontend/src/pages/signin/+Page.vue @@ -6,6 +6,7 @@ import { navigate } from 'vike/client/router' import { inject, onBeforeMount } from 'vue' +import GlobalErrorHandler from '#plugins/GlobalErrorHandler' import AuthService from '#src/services/AuthService' import { useAuthStore } from '#stores/authStore' @@ -26,8 +27,7 @@ onBeforeMount(async () => { await authService?.signIn() navigate('/') } catch (error) { - // eslint-disable-next-line no-console - console.log('auth error', error) + GlobalErrorHandler.error('auth error', error) } }) diff --git a/frontend/src/pages/silent-refresh/+Page.vue b/frontend/src/pages/silent-refresh/+Page.vue index 708ea5e39..f3ce3c2c6 100644 --- a/frontend/src/pages/silent-refresh/+Page.vue +++ b/frontend/src/pages/silent-refresh/+Page.vue @@ -9,6 +9,7 @@ import { navigate } from 'vike/client/router' import { inject, onBeforeMount } from 'vue' import DefaultLayout from '#layouts/DefaultLayout.vue' +import GlobalErrorHandler from '#plugins/GlobalErrorHandler' import AuthService from '#src/services/AuthService' const authService = inject('authService') @@ -18,8 +19,7 @@ onBeforeMount(async () => { await authService?.renewToken() navigate('/') } catch (error) { - // eslint-disable-next-line no-console - console.log('auth error', error) + GlobalErrorHandler.error('auth error', error) } }) From 9438567093fc6b0fe37589264ea0f4fd8207dfe0 Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Mon, 17 Jun 2024 16:22:05 +0200 Subject: [PATCH 2/8] added suppress eslint error for import --- frontend/renderer/app.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/renderer/app.ts b/frontend/renderer/app.ts index 228810b59..642d42ce0 100644 --- a/frontend/renderer/app.ts +++ b/frontend/renderer/app.ts @@ -3,6 +3,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import { PageContext } from 'vike/types' import { createSSRApp, defineComponent, h, markRaw, reactive, Component, provide } from 'vue' import Vue3Toasity from 'vue3-toastify' +// eslint-disable-next-line import/no-unassigned-import import 'vue3-toastify/dist/index.css' import PageShell from '#components/PageShell.vue' From 4d179832a0170266b998115cc41306171f9e5f41 Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Mon, 17 Jun 2024 16:22:32 +0200 Subject: [PATCH 3/8] removed test/debug code from GlobalErrorHandler.ts --- frontend/renderer/plugins/GlobalErrorHandler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/renderer/plugins/GlobalErrorHandler.ts b/frontend/renderer/plugins/GlobalErrorHandler.ts index 2cd9a2422..4db8e478f 100644 --- a/frontend/renderer/plugins/GlobalErrorHandler.ts +++ b/frontend/renderer/plugins/GlobalErrorHandler.ts @@ -4,8 +4,7 @@ import { toast } from 'vue3-toastify' const handleError = (message: string, data?: unknown) => { // eslint-disable-next-line no-console console.error('error: ' + message, data) - const id = toast.error(message, { delay: 0 }) - console.error(toast.isActive(id)) + toast.error(message, { delay: 0 }) } const handleWarning = (message: string) => { // eslint-disable-next-line no-console From 8528d44fee73d792fadef6ab64d1915b88ee6342 Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Tue, 18 Jun 2024 09:20:22 +0200 Subject: [PATCH 4/8] fixed output of undefined values in GlobalErrorHandler.error --- frontend/renderer/plugins/GlobalErrorHandler.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/renderer/plugins/GlobalErrorHandler.ts b/frontend/renderer/plugins/GlobalErrorHandler.ts index 4db8e478f..648199b8e 100644 --- a/frontend/renderer/plugins/GlobalErrorHandler.ts +++ b/frontend/renderer/plugins/GlobalErrorHandler.ts @@ -2,9 +2,14 @@ import { App } from 'vue' import { toast } from 'vue3-toastify' const handleError = (message: string, data?: unknown) => { - // eslint-disable-next-line no-console - console.error('error: ' + message, data) - toast.error(message, { delay: 0 }) + if (data) { + // eslint-disable-next-line no-console + console.error('error: ' + message, data) + } else { + // eslint-disable-next-line no-console + console.error('error: ' + message) + } + toast.error(message) } const handleWarning = (message: string) => { // eslint-disable-next-line no-console From f905792f77b278765a683490ba904dbf1b965eae Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Tue, 18 Jun 2024 09:21:27 +0200 Subject: [PATCH 5/8] replaced console error log with GlobalErrorHandler --- frontend/src/components/embedded-room/useMyRoom.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/embedded-room/useMyRoom.ts b/frontend/src/components/embedded-room/useMyRoom.ts index 3418b387f..80ba5afc3 100644 --- a/frontend/src/components/embedded-room/useMyRoom.ts +++ b/frontend/src/components/embedded-room/useMyRoom.ts @@ -1,6 +1,7 @@ import { useQuery } from '@vue/apollo-composable' import { watch, ref } from 'vue' +import GlobalErrorHandler from '#plugins/GlobalErrorHandler' import { JoinMyRoomQueryResult, joinMyRoomQuery } from '#queries/joinMyRoomQuery' export default function useMyRoom() { @@ -20,8 +21,7 @@ export default function useMyRoom() { watch(joinMyRoomQueryError, () => { if (joinMyRoomQueryError.value) { - // eslint-disable-next-line no-console - console.log(joinMyRoomQueryError.value.message) + GlobalErrorHandler.error(joinMyRoomQueryError.value.message) } }) From 925c372b01026a6ba3390bff36b109af211fd8bc Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Tue, 18 Jun 2024 09:21:49 +0200 Subject: [PATCH 6/8] fixed tests --- frontend/src/components/embedded-room/useMyRoom.test.ts | 4 ++-- frontend/src/components/menu/BottomMenu.test.ts | 4 ++-- frontend/src/components/menu/TopMenu.test.ts | 4 ++-- frontend/src/pages/auth/Page.test.ts | 4 ++-- frontend/src/pages/room/Page.test.ts | 4 ++-- frontend/src/pages/signin/Page.test.ts | 4 ++-- frontend/src/pages/silent-refresh/Page.test.ts | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/embedded-room/useMyRoom.test.ts b/frontend/src/components/embedded-room/useMyRoom.test.ts index 5040852d0..8e96df6cf 100644 --- a/frontend/src/components/embedded-room/useMyRoom.test.ts +++ b/frontend/src/components/embedded-room/useMyRoom.test.ts @@ -54,9 +54,9 @@ describe('useMyRoom', () => { }) it('logs error message', async () => { - const consoleSpy = vi.spyOn(global.console, 'log') + const consoleSpy = vi.spyOn(global.console, 'error') await flushPromises() - expect(consoleSpy).toBeCalledWith(errorMessage) + expect(consoleSpy).toBeCalledWith('error: ' + errorMessage) }) }) }) diff --git a/frontend/src/components/menu/BottomMenu.test.ts b/frontend/src/components/menu/BottomMenu.test.ts index 2a0f2b59b..40853772f 100644 --- a/frontend/src/components/menu/BottomMenu.test.ts +++ b/frontend/src/components/menu/BottomMenu.test.ts @@ -72,7 +72,7 @@ describe('BottomMenu', () => { }) describe('with error', () => { - const consoleSpy = vi.spyOn(console, 'log') + const consoleSpy = vi.spyOn(console, 'error') beforeEach(async () => { authServiceSpy.mockRejectedValue('Error!') @@ -82,7 +82,7 @@ describe('BottomMenu', () => { }) it('logs the error', () => { - expect(consoleSpy).toBeCalledWith('auth error', 'Error!') + expect(consoleSpy).toBeCalledWith('error: auth error', 'Error!') }) }) }) diff --git a/frontend/src/components/menu/TopMenu.test.ts b/frontend/src/components/menu/TopMenu.test.ts index 49ea81567..1b759da53 100644 --- a/frontend/src/components/menu/TopMenu.test.ts +++ b/frontend/src/components/menu/TopMenu.test.ts @@ -74,7 +74,7 @@ describe('TopMenu', () => { }) describe('with error', () => { - const consoleSpy = vi.spyOn(console, 'log') + const consoleSpy = vi.spyOn(console, 'error') beforeEach(async () => { authServiceSpy.mockRejectedValue('Error!') @@ -84,7 +84,7 @@ describe('TopMenu', () => { }) it('logs the error', () => { - expect(consoleSpy).toBeCalledWith('auth error', 'Error!') + expect(consoleSpy).toBeCalledWith('error: auth error', 'Error!') }) }) }) diff --git a/frontend/src/pages/auth/Page.test.ts b/frontend/src/pages/auth/Page.test.ts index 9076d005a..de0fb0ba2 100644 --- a/frontend/src/pages/auth/Page.test.ts +++ b/frontend/src/pages/auth/Page.test.ts @@ -82,7 +82,7 @@ describe('AuthPage', () => { }) describe('signin callback with error', () => { - const consoleSpy = vi.spyOn(global.console, 'log') + const consoleSpy = vi.spyOn(global.console, 'error') beforeEach(() => { vi.clearAllMocks() @@ -91,7 +91,7 @@ describe('AuthPage', () => { }) it('logs the error on console', () => { - expect(consoleSpy).toBeCalledWith('auth error', 'Ouch!') + expect(consoleSpy).toBeCalledWith('error: auth error', 'Ouch!') }) }) }) diff --git a/frontend/src/pages/room/Page.test.ts b/frontend/src/pages/room/Page.test.ts index 183481fcc..4919c3c2b 100644 --- a/frontend/src/pages/room/Page.test.ts +++ b/frontend/src/pages/room/Page.test.ts @@ -61,9 +61,9 @@ describe('Room Page', () => { }) it('logs error message', async () => { - const consoleSpy = vi.spyOn(global.console, 'log') + const consoleSpy = vi.spyOn(global.console, 'error') await flushPromises() - expect(consoleSpy).toBeCalledWith(errorMessage) + expect(consoleSpy).toBeCalledWith('error: ' + errorMessage) }) }) }) diff --git a/frontend/src/pages/signin/Page.test.ts b/frontend/src/pages/signin/Page.test.ts index fefc516b8..ea4d8eb3a 100644 --- a/frontend/src/pages/signin/Page.test.ts +++ b/frontend/src/pages/signin/Page.test.ts @@ -46,7 +46,7 @@ describe('SigninPage', () => { }) describe('signin with error', () => { - const consoleSpy = vi.spyOn(global.console, 'log') + const consoleSpy = vi.spyOn(global.console, 'error') beforeEach(() => { vi.clearAllMocks() @@ -55,7 +55,7 @@ describe('SigninPage', () => { }) it('logs the error on console', () => { - expect(consoleSpy).toBeCalledWith('auth error', 'Ouch!') + expect(consoleSpy).toBeCalledWith('error: auth error', 'Ouch!') }) }) }) diff --git a/frontend/src/pages/silent-refresh/Page.test.ts b/frontend/src/pages/silent-refresh/Page.test.ts index f8e57d06c..15b5c7021 100644 --- a/frontend/src/pages/silent-refresh/Page.test.ts +++ b/frontend/src/pages/silent-refresh/Page.test.ts @@ -49,7 +49,7 @@ describe('SilentRefreshPage', () => { }) describe('auth service throws', () => { - const consoleSpy = vi.spyOn(global.console, 'log') + const consoleSpy = vi.spyOn(global.console, 'error') beforeEach(() => { authServiceSpy.mockRejectedValue('Ouch!') @@ -57,7 +57,7 @@ describe('SilentRefreshPage', () => { }) it('logs error to console', () => { - expect(consoleSpy).toBeCalledWith('auth error', 'Ouch!') + expect(consoleSpy).toBeCalledWith('error: auth error', 'Ouch!') }) }) }) From 2bbbe0bf4974bb5e3b06a7683066c14143d8e1d6 Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Tue, 18 Jun 2024 09:38:09 +0200 Subject: [PATCH 7/8] added toast popup test again (still not working) --- frontend/src/pages/room/Page.test.ts | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/frontend/src/pages/room/Page.test.ts b/frontend/src/pages/room/Page.test.ts index 4919c3c2b..792b75fbb 100644 --- a/frontend/src/pages/room/Page.test.ts +++ b/frontend/src/pages/room/Page.test.ts @@ -66,4 +66,64 @@ describe('Room Page', () => { expect(consoleSpy).toBeCalledWith('error: ' + errorMessage) }) }) + + describe('with error and toast popup', () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) + const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined) + const closeButtonSelector = 'button.Toastify__close-button' + + // vi.useFakeTimers() + + beforeEach(() => { + vi.clearAllMocks() + joinMyRoomQueryMock.mockRejectedValue({ message: 'Aua!' }) + }) + + beforeEach(async () => { + await wrapper.find('button.room-button').trigger('click') + // await flushPromises() + // await vi.advanceTimersByTimeAsync(1000) + }) + + it('exists', () => { + // await new Promise((resolve) => setTimeout(resolve, 1000)) + // expect(consoleError).toBeCalledWith('test') + // expect(consoleError).toHaveBeenCalledOnce() + expect(consoleError).toHaveBeenLastCalledWith('error: test', undefined) + expect(consoleLog).toHaveBeenCalledTimes(0) + + expect(wrapper.find('div.Toastify').exists()).toBe(true) + expect(wrapper.find(closeButtonSelector).exists()).toBe(true) + }) + + describe('close by click', () => { + beforeEach(async () => { + await wrapper.find(closeButtonSelector).trigger('click') + }) + + it('does not exist', () => { + expect(wrapper.find(closeButtonSelector).exists()).toBe(false) + }) + }) + }) + + // describe('Toastify plugin', () => { + // it('shows a toast message', async () => { + // const wrapper = mount(VApp, { + // global: { + // plugins: [Vue3Toasity], + // }, + // }) + // + // vi.useFakeTimers() + // + // toast.error('Test Toast') + // await vi.advanceTimersByTimeAsync(1000) + // + // // await new Promise((resolve) => setTimeout(resolve, 1000)) + // await flushPromises() + // + // expect(wrapper.find('div.Toastify').exists()).toBe(true) + // }) + // }) }) From 5c5fcf218a853244be87f9a46f649e042bfeab19 Mon Sep 17 00:00:00 2001 From: Sebastian Stein Date: Wed, 19 Jun 2024 18:57:47 +0200 Subject: [PATCH 8/8] - removed console outputs - added error- and warningHandlerSpy as global test plugin - replaced all consoleSpies with errorHandlerSpies --- frontend/renderer/app.ts | 2 +- .../plugins/globalErrorHandler.spec.ts | 35 ++++++++++ ...lErrorHandler.ts => globalErrorHandler.ts} | 10 +-- .../tests/plugin.globalErrorHandler.ts | 9 +++ .../{useMyRoom.test.ts => useMyRoom.spec.ts} | 8 +-- .../src/components/embedded-room/useMyRoom.ts | 2 +- .../src/components/menu/BottomMenu.test.ts | 5 +- frontend/src/components/menu/TopMenu.test.ts | 5 +- frontend/src/components/menu/UserDropdown.vue | 2 +- frontend/src/pages/auth/+Page.vue | 2 +- frontend/src/pages/auth/Page.test.ts | 5 +- frontend/src/pages/room/Page.test.ts | 68 +------------------ frontend/src/pages/signin/+Page.vue | 2 +- frontend/src/pages/signin/Page.test.ts | 5 +- frontend/src/pages/silent-refresh/+Page.vue | 2 +- .../src/pages/silent-refresh/Page.test.ts | 5 +- 16 files changed, 67 insertions(+), 100 deletions(-) create mode 100644 frontend/renderer/plugins/globalErrorHandler.spec.ts rename frontend/renderer/plugins/{GlobalErrorHandler.ts => globalErrorHandler.ts} (61%) create mode 100644 frontend/scripts/tests/plugin.globalErrorHandler.ts rename frontend/src/components/embedded-room/{useMyRoom.test.ts => useMyRoom.spec.ts} (84%) diff --git a/frontend/renderer/app.ts b/frontend/renderer/app.ts index 642d42ce0..afdd31d69 100644 --- a/frontend/renderer/app.ts +++ b/frontend/renderer/app.ts @@ -9,7 +9,7 @@ import 'vue3-toastify/dist/index.css' import PageShell from '#components/PageShell.vue' import { setPageContext } from '#context/usePageContext' import { createApolloClient } from '#plugins/apollo' -import GlobalErrorHandler from '#plugins/GlobalErrorHandler' +import GlobalErrorHandler from '#plugins/globalErrorHandler' import i18n from '#plugins/i18n' import pinia from '#plugins/pinia' import CreateVuetify from '#plugins/vuetify' diff --git a/frontend/renderer/plugins/globalErrorHandler.spec.ts b/frontend/renderer/plugins/globalErrorHandler.spec.ts new file mode 100644 index 000000000..3db891042 --- /dev/null +++ b/frontend/renderer/plugins/globalErrorHandler.spec.ts @@ -0,0 +1,35 @@ +import { vi, describe, it, expect } from 'vitest' +import { toast } from 'vue3-toastify' + +import globalErrorHandler from './globalErrorHandler' + +// vi.mock('vue3-toastify', async (importOriginal) => { +// const mod = await importOriginal() +// return { +// ...mod, +// error: vi.fn(), +// warning: vi.fn(), +// } +// }) + +describe('GlobalErrorHandler', () => { + describe('Error', () => { + const errorSpy = vi.spyOn(toast, 'error') + + it('toasts error message', () => { + globalErrorHandler.error('someError') + + expect(errorSpy).toBeCalledWith('someError') + }) + }) + + describe('Warning', () => { + const warningSpy = vi.spyOn(toast, 'warning') + + it('toasts warning message', () => { + globalErrorHandler.warning('someWarning') + + expect(warningSpy).toBeCalledWith('someWarning') + }) + }) +}) diff --git a/frontend/renderer/plugins/GlobalErrorHandler.ts b/frontend/renderer/plugins/globalErrorHandler.ts similarity index 61% rename from frontend/renderer/plugins/GlobalErrorHandler.ts rename to frontend/renderer/plugins/globalErrorHandler.ts index 648199b8e..3b2f21674 100644 --- a/frontend/renderer/plugins/GlobalErrorHandler.ts +++ b/frontend/renderer/plugins/globalErrorHandler.ts @@ -1,19 +1,11 @@ import { App } from 'vue' import { toast } from 'vue3-toastify' +// eslint-disable-next-line @typescript-eslint/no-unused-vars const handleError = (message: string, data?: unknown) => { - if (data) { - // eslint-disable-next-line no-console - console.error('error: ' + message, data) - } else { - // eslint-disable-next-line no-console - console.error('error: ' + message) - } toast.error(message) } const handleWarning = (message: string) => { - // eslint-disable-next-line no-console - console.log('warning: ' + message) toast.warning(message) } diff --git a/frontend/scripts/tests/plugin.globalErrorHandler.ts b/frontend/scripts/tests/plugin.globalErrorHandler.ts new file mode 100644 index 000000000..b475b4388 --- /dev/null +++ b/frontend/scripts/tests/plugin.globalErrorHandler.ts @@ -0,0 +1,9 @@ +import { config } from '@vue/test-utils' +import { vi } from 'vitest' + +import globalErrorHandler from '#plugins/globalErrorHandler' + +export const errorHandlerSpy = vi.spyOn(globalErrorHandler, 'error') +export const warningHandlerSpy = vi.spyOn(globalErrorHandler, 'warning') + +config.global.plugins.push(globalErrorHandler) diff --git a/frontend/src/components/embedded-room/useMyRoom.test.ts b/frontend/src/components/embedded-room/useMyRoom.spec.ts similarity index 84% rename from frontend/src/components/embedded-room/useMyRoom.test.ts rename to frontend/src/components/embedded-room/useMyRoom.spec.ts index 8e96df6cf..b0ed5e144 100644 --- a/frontend/src/components/embedded-room/useMyRoom.test.ts +++ b/frontend/src/components/embedded-room/useMyRoom.spec.ts @@ -4,6 +4,7 @@ import { defineComponent } from 'vue' import { joinMyRoomQuery } from '#queries/joinMyRoomQuery' import { mockClient } from '#tests/mock.apolloClient' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import useMyRoom from './useMyRoom' @@ -44,19 +45,16 @@ describe('useMyRoom', () => { }) describe('with apollo error', () => { - const errorMessage = 'Aua!' - beforeEach(() => { wrapper.unmount() vi.clearAllMocks() - joinMyRoomQueryMock.mockRejectedValue({ message: errorMessage, data: undefined }) + joinMyRoomQueryMock.mockRejectedValue({ message: 'Aua!', data: undefined }) wrapper = Wrapper() }) it('logs error message', async () => { - const consoleSpy = vi.spyOn(global.console, 'error') await flushPromises() - expect(consoleSpy).toBeCalledWith('error: ' + errorMessage) + expect(errorHandlerSpy).toBeCalledWith('Aua!') }) }) }) diff --git a/frontend/src/components/embedded-room/useMyRoom.ts b/frontend/src/components/embedded-room/useMyRoom.ts index 80ba5afc3..211562530 100644 --- a/frontend/src/components/embedded-room/useMyRoom.ts +++ b/frontend/src/components/embedded-room/useMyRoom.ts @@ -1,7 +1,7 @@ import { useQuery } from '@vue/apollo-composable' import { watch, ref } from 'vue' -import GlobalErrorHandler from '#plugins/GlobalErrorHandler' +import GlobalErrorHandler from '#plugins/globalErrorHandler' import { JoinMyRoomQueryResult, joinMyRoomQuery } from '#queries/joinMyRoomQuery' export default function useMyRoom() { diff --git a/frontend/src/components/menu/BottomMenu.test.ts b/frontend/src/components/menu/BottomMenu.test.ts index 40853772f..70f00c2da 100644 --- a/frontend/src/components/menu/BottomMenu.test.ts +++ b/frontend/src/components/menu/BottomMenu.test.ts @@ -5,6 +5,7 @@ import { VApp } from 'vuetify/components' import { useAuthStore } from '#stores/authStore.js' import { authService } from '#tests/mock.authService.js' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import BottomMenu from './BottomMenu.vue' import UserDropdown from './UserDropdown.vue' @@ -72,8 +73,6 @@ describe('BottomMenu', () => { }) describe('with error', () => { - const consoleSpy = vi.spyOn(console, 'error') - beforeEach(async () => { authServiceSpy.mockRejectedValue('Error!') await wrapper.find('button.user-info').trigger('click') @@ -82,7 +81,7 @@ describe('BottomMenu', () => { }) it('logs the error', () => { - expect(consoleSpy).toBeCalledWith('error: auth error', 'Error!') + expect(errorHandlerSpy).toBeCalledWith('auth error', 'Error!') }) }) }) diff --git a/frontend/src/components/menu/TopMenu.test.ts b/frontend/src/components/menu/TopMenu.test.ts index 1b759da53..994f09f08 100644 --- a/frontend/src/components/menu/TopMenu.test.ts +++ b/frontend/src/components/menu/TopMenu.test.ts @@ -6,6 +6,7 @@ import { VApp } from 'vuetify/components' import { AUTH } from '#src/env' import { useAuthStore } from '#stores/authStore' import { authService } from '#tests/mock.authService' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import TopMenu from './TopMenu.vue' import UserDropdown from './UserDropdown.vue' @@ -74,8 +75,6 @@ describe('TopMenu', () => { }) describe('with error', () => { - const consoleSpy = vi.spyOn(console, 'error') - beforeEach(async () => { authServiceSpy.mockRejectedValue('Error!') await wrapper.find('button.user-info').trigger('click') @@ -84,7 +83,7 @@ describe('TopMenu', () => { }) it('logs the error', () => { - expect(consoleSpy).toBeCalledWith('error: auth error', 'Error!') + expect(errorHandlerSpy).toBeCalledWith('auth error', 'Error!') }) }) }) diff --git a/frontend/src/components/menu/UserDropdown.vue b/frontend/src/components/menu/UserDropdown.vue index d03467c40..916d61533 100644 --- a/frontend/src/components/menu/UserDropdown.vue +++ b/frontend/src/components/menu/UserDropdown.vue @@ -18,7 +18,7 @@ import { inject } from 'vue' import MainButton from '#components/buttons/MainButton.vue' -import GlobalErrorHandler from '#plugins/GlobalErrorHandler' +import GlobalErrorHandler from '#plugins/globalErrorHandler' import { AUTH } from '#src/env' import AuthService from '#src/services/AuthService' import { useAuthStore } from '#stores/authStore' diff --git a/frontend/src/pages/auth/+Page.vue b/frontend/src/pages/auth/+Page.vue index 179a2f8fc..e29e2dca3 100644 --- a/frontend/src/pages/auth/+Page.vue +++ b/frontend/src/pages/auth/+Page.vue @@ -9,7 +9,7 @@ import { navigate } from 'vike/client/router' import { inject, onBeforeMount } from 'vue' import DefaultLayout from '#layouts/DefaultLayout.vue' -import GlobalErrorHandler from '#plugins/GlobalErrorHandler' +import GlobalErrorHandler from '#plugins/globalErrorHandler' import AuthService from '#src/services/AuthService' const authService = inject('authService') diff --git a/frontend/src/pages/auth/Page.test.ts b/frontend/src/pages/auth/Page.test.ts index de0fb0ba2..02da95db3 100644 --- a/frontend/src/pages/auth/Page.test.ts +++ b/frontend/src/pages/auth/Page.test.ts @@ -6,6 +6,7 @@ import { VApp } from 'vuetify/components' import i18n from '#plugins/i18n' import { authService } from '#tests/mock.authService' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import AuthPage from './+Page.vue' import { title } from './+title' @@ -82,8 +83,6 @@ describe('AuthPage', () => { }) describe('signin callback with error', () => { - const consoleSpy = vi.spyOn(global.console, 'error') - beforeEach(() => { vi.clearAllMocks() authServiceSpy.mockRejectedValue('Ouch!') @@ -91,7 +90,7 @@ describe('AuthPage', () => { }) it('logs the error on console', () => { - expect(consoleSpy).toBeCalledWith('error: auth error', 'Ouch!') + expect(errorHandlerSpy).toBeCalledWith('auth error', 'Ouch!') }) }) }) diff --git a/frontend/src/pages/room/Page.test.ts b/frontend/src/pages/room/Page.test.ts index 792b75fbb..fe4f82cc6 100644 --- a/frontend/src/pages/room/Page.test.ts +++ b/frontend/src/pages/room/Page.test.ts @@ -5,6 +5,7 @@ import { VApp } from 'vuetify/components' import { joinMyRoomQuery } from '#queries/joinMyRoomQuery' import { mockClient } from '#tests/mock.apolloClient' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import RoomPage from './+Page.vue' import { title } from './+title' @@ -52,78 +53,15 @@ describe('Room Page', () => { }) describe('with apollo error', () => { - const errorMessage = 'Aua!' - beforeEach(() => { vi.clearAllMocks() - joinMyRoomQueryMock.mockRejectedValue({ message: errorMessage, data: undefined }) + joinMyRoomQueryMock.mockRejectedValue({ message: 'Aua!', data: undefined }) wrapper = Wrapper() }) it('logs error message', async () => { - const consoleSpy = vi.spyOn(global.console, 'error') await flushPromises() - expect(consoleSpy).toBeCalledWith('error: ' + errorMessage) + expect(errorHandlerSpy).toBeCalledWith('Aua!') }) }) - - describe('with error and toast popup', () => { - const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) - const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined) - const closeButtonSelector = 'button.Toastify__close-button' - - // vi.useFakeTimers() - - beforeEach(() => { - vi.clearAllMocks() - joinMyRoomQueryMock.mockRejectedValue({ message: 'Aua!' }) - }) - - beforeEach(async () => { - await wrapper.find('button.room-button').trigger('click') - // await flushPromises() - // await vi.advanceTimersByTimeAsync(1000) - }) - - it('exists', () => { - // await new Promise((resolve) => setTimeout(resolve, 1000)) - // expect(consoleError).toBeCalledWith('test') - // expect(consoleError).toHaveBeenCalledOnce() - expect(consoleError).toHaveBeenLastCalledWith('error: test', undefined) - expect(consoleLog).toHaveBeenCalledTimes(0) - - expect(wrapper.find('div.Toastify').exists()).toBe(true) - expect(wrapper.find(closeButtonSelector).exists()).toBe(true) - }) - - describe('close by click', () => { - beforeEach(async () => { - await wrapper.find(closeButtonSelector).trigger('click') - }) - - it('does not exist', () => { - expect(wrapper.find(closeButtonSelector).exists()).toBe(false) - }) - }) - }) - - // describe('Toastify plugin', () => { - // it('shows a toast message', async () => { - // const wrapper = mount(VApp, { - // global: { - // plugins: [Vue3Toasity], - // }, - // }) - // - // vi.useFakeTimers() - // - // toast.error('Test Toast') - // await vi.advanceTimersByTimeAsync(1000) - // - // // await new Promise((resolve) => setTimeout(resolve, 1000)) - // await flushPromises() - // - // expect(wrapper.find('div.Toastify').exists()).toBe(true) - // }) - // }) }) diff --git a/frontend/src/pages/signin/+Page.vue b/frontend/src/pages/signin/+Page.vue index ec2b18568..a11b97c96 100644 --- a/frontend/src/pages/signin/+Page.vue +++ b/frontend/src/pages/signin/+Page.vue @@ -6,7 +6,7 @@ import { navigate } from 'vike/client/router' import { inject, onBeforeMount } from 'vue' -import GlobalErrorHandler from '#plugins/GlobalErrorHandler' +import GlobalErrorHandler from '#plugins/globalErrorHandler' import AuthService from '#src/services/AuthService' import { useAuthStore } from '#stores/authStore' diff --git a/frontend/src/pages/signin/Page.test.ts b/frontend/src/pages/signin/Page.test.ts index ea4d8eb3a..79cf4df49 100644 --- a/frontend/src/pages/signin/Page.test.ts +++ b/frontend/src/pages/signin/Page.test.ts @@ -7,6 +7,7 @@ import { VApp } from 'vuetify/components' import i18n from '#plugins/i18n' import { useAuthStore } from '#stores/authStore' import { authService } from '#tests/mock.authService' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import SigninPage from './+Page.vue' import { title } from './+title' @@ -46,8 +47,6 @@ describe('SigninPage', () => { }) describe('signin with error', () => { - const consoleSpy = vi.spyOn(global.console, 'error') - beforeEach(() => { vi.clearAllMocks() authServiceSpy.mockRejectedValue('Ouch!') @@ -55,7 +54,7 @@ describe('SigninPage', () => { }) it('logs the error on console', () => { - expect(consoleSpy).toBeCalledWith('error: auth error', 'Ouch!') + expect(errorHandlerSpy).toBeCalledWith('auth error', 'Ouch!') }) }) }) diff --git a/frontend/src/pages/silent-refresh/+Page.vue b/frontend/src/pages/silent-refresh/+Page.vue index f3ce3c2c6..bd9302e19 100644 --- a/frontend/src/pages/silent-refresh/+Page.vue +++ b/frontend/src/pages/silent-refresh/+Page.vue @@ -9,7 +9,7 @@ import { navigate } from 'vike/client/router' import { inject, onBeforeMount } from 'vue' import DefaultLayout from '#layouts/DefaultLayout.vue' -import GlobalErrorHandler from '#plugins/GlobalErrorHandler' +import GlobalErrorHandler from '#plugins/globalErrorHandler' import AuthService from '#src/services/AuthService' const authService = inject('authService') diff --git a/frontend/src/pages/silent-refresh/Page.test.ts b/frontend/src/pages/silent-refresh/Page.test.ts index 15b5c7021..7ab5dcb5c 100644 --- a/frontend/src/pages/silent-refresh/Page.test.ts +++ b/frontend/src/pages/silent-refresh/Page.test.ts @@ -6,6 +6,7 @@ import { VApp } from 'vuetify/components' import i18n from '#plugins/i18n' import { authService } from '#tests/mock.authService' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' import SilentRefreshPage from './+Page.vue' import { title } from './+title' @@ -49,15 +50,13 @@ describe('SilentRefreshPage', () => { }) describe('auth service throws', () => { - const consoleSpy = vi.spyOn(global.console, 'error') - beforeEach(() => { authServiceSpy.mockRejectedValue('Ouch!') wrapper = Wrapper() }) it('logs error to console', () => { - expect(consoleSpy).toBeCalledWith('error: auth error', 'Ouch!') + expect(errorHandlerSpy).toBeCalledWith('auth error', 'Ouch!') }) }) })