-
{{
- $t('error')
- }}
-
{{
- $t('success')
- }}
-
{{
- $t('warning')
- }}
-
{{
- $t('info')
- }}
+
+
+ {{ $t('error') }}
+
+
+
+ {{ $t('success') }}
+
+
+
+ {{ $t('warning') }}
+
+
+
+ {{ $t('info') }}
+
diff --git a/app/stores/auth/password.ts b/app/stores/auth/password.ts
index 5212d0b19..bab3b64df 100644
--- a/app/stores/auth/password.ts
+++ b/app/stores/auth/password.ts
@@ -58,10 +58,10 @@ export const usePasswordStore = defineStore('password', () => {
const errorCode = error.data?.data?.error?.code;
return [{ field: 'identifier', code: errorCode }];
} else if (error.data?.statusCode === 429) {
- showToaster('error', 'toaster.checkUser.rateLimit');
+ showToaster('error', 'toaster.checkUser.rateLimit', true);
}
} else {
- showToaster('error', 'toaster.checkUser.error');
+ showToaster('error', 'toaster.checkUser.error', true);
}
} finally {
loading.value = false;
@@ -82,7 +82,7 @@ export const usePasswordStore = defineStore('password', () => {
const errors = error.data?.data?.error.errors;
return errors;
} else {
- showToaster('error', 'toaster.verifyUser.error');
+ showToaster('error', 'toaster.verifyUser.error', true);
}
} finally {
loading.value = false;
@@ -93,17 +93,17 @@ export const usePasswordStore = defineStore('password', () => {
try {
await passwordService.resendOtp(confirmationToken.value);
step.value = 1;
- showToaster('success', 'toaster.resendOtp.success');
+ showToaster('success', 'toaster.resendOtp.success', true);
} catch (error) {
if (isApiError(error) && error.status === 429) {
const apiError = error.data?.data;
- showToaster('error', apiError?.message || 'toaster.resendOtp.rateLimit');
+ showToaster('error', apiError?.message || 'toaster.resendOtp.rateLimit', true);
const { retryAfter } = apiError?.error as unknown as { retryAfter: number };
if (retryAfter) {
return retryAfter;
}
} else {
- showToaster('error', 'toaster.resendOtp.error');
+ showToaster('error', 'toaster.resendOtp.error', true);
}
}
};
@@ -118,14 +118,14 @@ export const usePasswordStore = defineStore('password', () => {
await passwordService.resetPassword(data);
resetData();
open.value = false;
- showToaster('success', 'toaster.resetPassword.success');
+ showToaster('success', 'toaster.resetPassword.success', true);
router.push('/home');
} catch (error) {
if (isApiValidationError(error)) {
const errors = error.data?.data?.error.errors;
return errors;
} else {
- showToaster('error', 'toaster.resetPassword.error');
+ showToaster('error', 'toaster.resetPassword.error', true);
}
} finally {
loading.value = false;
diff --git a/app/utils/showToaster.ts b/app/utils/showToaster.ts
index 008fc4849..ba7da9d4a 100644
--- a/app/utils/showToaster.ts
+++ b/app/utils/showToaster.ts
@@ -1,43 +1,13 @@
import { toast } from 'vue-sonner';
import { useNuxtApp } from '#app';
-const typeConfig = {
- success: {
- icon: '✔️',
- bg: 'var(--toaster-bg-success)',
- color: 'var(--toaster-text-success)',
- },
- error: {
- icon: '❌',
- bg: 'var(--toaster-bg-error)',
- color: 'var(--toaster-text-error)',
- },
- warning: {
- icon: '⚠️',
- bg: 'var(--toaster-bg-warning)',
- color: 'var(--toaster-text-warning)',
- },
- info: {
- icon: 'ℹ️',
- bg: 'var(--toaster-bg-info)',
- color: 'var(--toaster-text-info)',
- },
-} as const;
-
-type ToastType = keyof typeof typeConfig;
-
-export function showToaster(type: ToastType, message: string, translate: boolean = false) {
- const { icon, bg, color } = typeConfig[type];
+export function showToaster(
+ type: 'success' | 'error' | 'warning' | 'info',
+ message: string,
+ translate = false,
+) {
const { $i18n } = useNuxtApp();
+ const text = translate ? $i18n.t(message) : message;
- const translatedMessage = translate ? $i18n.t(message) : message;
-
- toast(`${icon} ${translatedMessage}`, {
- style: {
- background: bg,
- color: color,
- padding: '10px 14px',
- borderRadius: '20px',
- },
- });
+ toast[type](text);
}
diff --git a/test/nuxt/components/ui/Toaster.spec.ts b/test/nuxt/components/ui/Toaster.spec.ts
index ce275ea5c..e0bc9f5c4 100644
--- a/test/nuxt/components/ui/Toaster.spec.ts
+++ b/test/nuxt/components/ui/Toaster.spec.ts
@@ -1,8 +1,6 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mountSuspended } from '@nuxt/test-utils/runtime';
import Toaster from '@/components/ui/Toaster.vue';
-import { showToaster } from '@/utils/showToaster';
-import { toast } from 'vue-sonner';
// Mock vue-sonner components and functions
vi.mock('vue-sonner', () => ({
@@ -21,8 +19,8 @@ describe('Toaster Component', () => {
it('renders with default position (bottom-right)', async () => {
const wrapper = await mountSuspended(Toaster);
const classes = wrapper.classes().join(' ');
- expect(classes).toContain('bottom-0');
- expect(classes).toContain('right-0');
+ expect(classes).toContain('bottom-4');
+ expect(classes).toContain('right-4');
});
it('renders with position top-left', async () => {
@@ -30,8 +28,8 @@ describe('Toaster Component', () => {
props: { position: 'top-left' },
});
const classes = wrapper.classes().join(' ');
- expect(classes).toContain('top-0');
- expect(classes).toContain('left-0');
+ expect(classes).toContain('top-4');
+ expect(classes).toContain('left-4');
});
it('renders with position top-center', async () => {
@@ -39,7 +37,8 @@ describe('Toaster Component', () => {
props: { position: 'top-center' },
});
const classes = wrapper.classes().join(' ');
- expect(classes).toContain('top-0');
+ expect(classes).toContain('top-4');
+ expect(classes).toContain('left-1/2');
expect(classes).toContain('-translate-x-1/2');
});
@@ -48,65 +47,8 @@ describe('Toaster Component', () => {
props: { position: 'bottom-center' },
});
const classes = wrapper.classes().join(' ');
- expect(classes).toContain('bottom-0');
+ expect(classes).toContain('bottom-4');
+ expect(classes).toContain('left-1/2');
expect(classes).toContain('-translate-x-1/2');
});
});
-
-describe('showToaster utility', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- it('calls toast with success icon and styles', () => {
- showToaster('success', 'Operation successful!');
- expect(toast).toHaveBeenCalledWith(
- expect.stringContaining('✔️ Operation successful!'),
- expect.objectContaining({
- style: expect.objectContaining({
- background: 'var(--toaster-bg-success)',
- color: 'var(--toaster-text-success)',
- }),
- }),
- );
- });
-
- it('calls toast with error icon and styles', () => {
- showToaster('error', 'Something went wrong!');
- expect(toast).toHaveBeenCalledWith(
- expect.stringContaining('❌ Something went wrong!'),
- expect.objectContaining({
- style: expect.objectContaining({
- background: 'var(--toaster-bg-error)',
- color: 'var(--toaster-text-error)',
- }),
- }),
- );
- });
-
- it('calls toast with warning icon and styles', () => {
- showToaster('warning', 'Be careful!');
- expect(toast).toHaveBeenCalledWith(
- expect.stringContaining('⚠️ Be careful!'),
- expect.objectContaining({
- style: expect.objectContaining({
- background: 'var(--toaster-bg-warning)',
- color: 'var(--toaster-text-warning)',
- }),
- }),
- );
- });
-
- it('calls toast with info icon and styles', () => {
- showToaster('info', 'Information here!');
- expect(toast).toHaveBeenCalledWith(
- expect.stringContaining('ℹ️ Information here!'),
- expect.objectContaining({
- style: expect.objectContaining({
- background: 'var(--toaster-bg-info)',
- color: 'var(--toaster-text-info)',
- }),
- }),
- );
- });
-});
diff --git a/test/nuxt/stores/password.spec.ts b/test/nuxt/stores/password.spec.ts
index f8c414041..1fa4ebf72 100644
--- a/test/nuxt/stores/password.spec.ts
+++ b/test/nuxt/stores/password.spec.ts
@@ -127,14 +127,14 @@ describe('Password Store', () => {
passwordServiceMock.checkUser.mockRejectedValue(rateLimitError);
const store = await createStore();
await store.checkUserExists({ identifier: 'test@example.com', recaptchaToken: 'recap' });
- expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.checkUser.rateLimit');
+ expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.checkUser.rateLimit', true);
});
it('shows toaster for generic errors', async () => {
passwordServiceMock.checkUser.mockRejectedValue(new Error('Network error'));
const store = await createStore();
await store.checkUserExists({ identifier: 'fail', recaptchaToken: 'recap' });
- expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.checkUser.error');
+ expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.checkUser.error', true);
});
});
@@ -176,7 +176,7 @@ describe('Password Store', () => {
const store = await createStore();
await store.checkUserExists({ identifier: 'a', recaptchaToken: 'b' });
await store.verifyUser('wrong');
- expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.verifyUser.error');
+ expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.verifyUser.error', true);
});
});
@@ -188,7 +188,7 @@ describe('Password Store', () => {
await store.checkUserExists({ identifier: 'a', recaptchaToken: 'b' });
await store.resendOtp();
expect(passwordServiceMock.resendOtp).toHaveBeenCalledWith('token');
- expect(showToasterMock).toHaveBeenCalledWith('success', 'toaster.resendOtp.success');
+ expect(showToasterMock).toHaveBeenCalledWith('success', 'toaster.resendOtp.success', true);
expect(store.step).toBe(1);
});
@@ -218,7 +218,7 @@ describe('Password Store', () => {
const store = await createStore();
await store.checkUserExists({ identifier: 'a', recaptchaToken: 'b' });
await store.resendOtp();
- expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.resendOtp.error');
+ expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.resendOtp.error', true);
});
});
@@ -231,7 +231,11 @@ describe('Password Store', () => {
const store = await createStore();
await store.checkUserExists({ identifier: 'x', recaptchaToken: 'y' });
await store.resetPassword('Pass123!');
- expect(showToasterMock).toHaveBeenCalledWith('success', 'toaster.resetPassword.success');
+ expect(showToasterMock).toHaveBeenCalledWith(
+ 'success',
+ 'toaster.resetPassword.success',
+ true,
+ );
expect(routerMock.push).toHaveBeenCalledWith('/home');
expect(store.step).toBe(0);
expect(store.open).toBe(false);
@@ -265,7 +269,7 @@ describe('Password Store', () => {
const store = await createStore();
await store.checkUserExists({ identifier: 'x', recaptchaToken: 'y' });
await store.resetPassword('fail');
- expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.resetPassword.error');
+ expect(showToasterMock).toHaveBeenCalledWith('error', 'toaster.resetPassword.error', true);
expect(store.loading).toBe(false);
});
});
diff --git a/test/nuxt/utils/showToaster.spec.ts b/test/nuxt/utils/showToaster.spec.ts
new file mode 100644
index 000000000..06cbc6ad3
--- /dev/null
+++ b/test/nuxt/utils/showToaster.spec.ts
@@ -0,0 +1,56 @@
+// tests/nuxt/utils/showToaster.spec.ts (or wherever your spec is)
+
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { showToaster } from '@/utils/showToaster';
+import { toast } from 'vue-sonner';
+
+// Properly mock useNuxtApp using the recommended Nuxt test-utils approach
+import { mockNuxtImport } from '@nuxt/test-utils/runtime';
+
+// Mock vue-sonner with the legacy typed methods (success, error, etc.)
+vi.mock('vue-sonner', () => ({
+ toast: {
+ success: vi.fn(),
+ error: vi.fn(),
+ warning: vi.fn(),
+ info: vi.fn(),
+ },
+}));
+
+mockNuxtImport('useNuxtApp', () => {
+ return () => ({
+ $i18n: {
+ t: vi.fn((msg: string) => `[translated: ${msg}]`),
+ },
+ });
+});
+
+describe('showToaster utility', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('calls toast.success with raw message when translate is false', () => {
+ showToaster('success', 'Operation successful!');
+
+ expect(toast.success).toHaveBeenCalledWith('Operation successful!');
+ });
+
+ it('calls toast.error correctly', () => {
+ showToaster('error', 'Something went wrong!');
+
+ expect(toast.error).toHaveBeenCalledWith('Something went wrong!');
+ });
+
+ it('calls toast.warning correctly', () => {
+ showToaster('warning', 'Be careful!');
+
+ expect(toast.warning).toHaveBeenCalledWith('Be careful!');
+ });
+
+ it('calls toast.info correctly', () => {
+ showToaster('info', 'Here is some info');
+
+ expect(toast.info).toHaveBeenCalledWith('Here is some info');
+ });
+});