Files
api/web/__test__/store/errors.test.ts
Michael Datelle 72860e71fe test: create tests for stores batch 3 (#1358)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added comprehensive test coverage for the purchase, replaceRenew,
modal, notifications, theme, trial, unraidApi, unraidApiSettings,
updateOs, updateOsActions, updateOsChangelog, activationCode, and
callbackActions stores.
- Exposed callback error state in the callbackActions store for external
access.
  - Made error state publicly accessible in the replaceRenew store.

- **Tests**
- Introduced new test files covering state, getters, actions, and side
effects for multiple stores including modal, notifications, purchase,
replaceRenew, theme, trial, unraidApi, unraidApiSettings, updateOs,
updateOsActions, updateOsChangelog, activationCode, and callbackActions.
- Enhanced existing test suites with additional mocks, reactive state
handling, and expanded test cases for improved coverage and robustness.

- **Refactor**
- Improved code clarity and readability in modal, notifications,
purchase, replaceRenew, trial, theme, updateOsActions, callbackActions,
and unraidApi stores through import reorganization and formatting
adjustments.
- Updated imports to include reactive and computed utilities for
enhanced state management in several stores.
- Standardized import styles and streamlined store definitions in the
unraidApiSettings store.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
2025-04-16 17:06:52 -04:00

229 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Errors store test coverage
*/
import { nextTick } from 'vue';
import { createPinia, setActivePinia } from 'pinia';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { Error } from '~/store/errors';
import { useErrorsStore } from '~/store/errors';
const mockFeedbackButton = vi.fn();
vi.mock('~/helpers/functions', () => ({
OBJ_TO_STR: (obj: unknown) => JSON.stringify(obj),
}));
vi.stubGlobal('FeedbackButton', mockFeedbackButton);
describe('Errors Store', () => {
let store: ReturnType<typeof useErrorsStore>;
const originalConsoleError = console.error;
const mockError: Error = {
heading: 'Test Error',
level: 'error',
message: 'Test message',
type: 'request',
ref: 'test-ref',
};
beforeEach(() => {
// Silence console.error during tests
console.error = vi.fn();
const pinia = createPinia();
setActivePinia(pinia);
store = useErrorsStore();
vi.clearAllMocks();
});
afterEach(() => {
console.error = originalConsoleError;
vi.resetAllMocks();
});
describe('State and Actions', () => {
it('should initialize with empty errors array', () => {
expect(store.errors).toEqual([]);
});
it('should add error', () => {
store.setError(mockError);
expect(store.errors).toHaveLength(1);
expect(store.errors[0]).toEqual(mockError);
});
it('should remove error by index', async () => {
store.setError(mockError);
store.setError({ ...mockError, ref: 'test-ref-2' });
expect(store.errors).toHaveLength(2);
store.removeErrorByIndex(0);
await nextTick();
expect(store.errors).toHaveLength(1);
expect(store.errors[0].ref).toBe('test-ref-2');
});
it('should remove error by ref', async () => {
store.setError(mockError);
store.setError({ ...mockError, ref: 'test-ref-2' });
expect(store.errors).toHaveLength(2);
store.removeErrorByRef('test-ref');
await nextTick();
expect(store.errors).toHaveLength(1);
expect(store.errors[0].ref).toBe('test-ref-2');
});
it('should reset errors', async () => {
store.setError(mockError);
store.setError({ ...mockError, ref: 'test-ref-2' });
expect(store.errors).toHaveLength(2);
store.resetErrors();
await nextTick();
expect(store.errors).toHaveLength(0);
});
it('should not change errors when removing by non-existent index', async () => {
store.setError(mockError);
const initialErrors = [...store.errors];
expect(initialErrors).toHaveLength(1);
store.removeErrorByIndex(99);
await nextTick();
expect(store.errors).toHaveLength(1);
expect(store.errors).toEqual(initialErrors);
});
it('should not change errors when removing by non-existent ref', async () => {
store.setError(mockError);
const initialErrors = [...store.errors];
expect(initialErrors).toHaveLength(1);
store.removeErrorByRef('non-existent-ref');
await nextTick();
expect(store.errors).toHaveLength(1);
expect(store.errors).toEqual(initialErrors);
});
});
describe('Troubleshoot Feature', () => {
beforeEach(() => {
const mockModal = document.createElement('div');
mockModal.className = 'sweet-alert visible';
const mockTextarea = document.createElement('textarea');
mockTextarea.id = 'troubleshootDetails';
mockModal.appendChild(mockTextarea);
const mockEmailInput = document.createElement('input');
mockEmailInput.id = 'troubleshootEmail';
mockModal.appendChild(mockEmailInput);
const mockRadio = document.createElement('input');
mockRadio.id = 'optTroubleshoot';
mockRadio.type = 'radio';
mockModal.appendChild(mockRadio);
const mockPanels = document.createElement('div');
mockPanels.className = 'allpanels';
mockPanels.id = 'troubleshoot_panel';
mockModal.appendChild(mockPanels);
document.body.appendChild(mockModal);
});
afterEach(() => {
document.body.innerHTML = '';
});
it('should open troubleshoot with multiple error details including debugServer', async () => {
const error1: Error = { ...mockError, ref: 'err1' };
const error2: Error = {
heading: 'Second Error',
level: 'warning',
message: 'Another message',
type: 'serverState',
ref: 'err2',
debugServer: { guid: 'debug-guid', name: 'debug-server' },
};
store.setError(error1);
store.setError(error2);
await nextTick();
await store.openTroubleshoot({
email: 'test@example.com',
includeUnraidApiLogs: true,
});
const textarea = document.querySelector('#troubleshootDetails') as HTMLTextAreaElement;
const emailInput = document.querySelector('#troubleshootEmail') as HTMLInputElement;
const radio = document.querySelector('#optTroubleshoot') as HTMLInputElement;
const panel = document.querySelector('#troubleshoot_panel') as HTMLElement;
expect(mockFeedbackButton).toHaveBeenCalled();
expect(textarea.value).toContain('Debug Details Component Errors 2');
expect(textarea.value).toContain('Error 1: Test Error');
expect(textarea.value).toContain('Error 1 Message: Test message');
expect(textarea.value).toContain('Error 1 Ref: err1');
expect(textarea.value).not.toContain('Error 1 Debug Server');
expect(textarea.value).toContain('\n***************\n');
expect(textarea.value).toContain('Error 2: Second Error');
expect(textarea.value).toContain('Error 2 Message: Another message');
expect(textarea.value).toContain('Error 2 Level: warning');
expect(textarea.value).toContain('Error 2 Type: serverState');
expect(textarea.value).toContain('Error 2 Ref: err2');
expect(textarea.value).toContain(
'Error 2 Debug Server:\n{"guid":"debug-guid","name":"debug-server"}'
);
expect(emailInput.value).toBe('test@example.com');
expect(radio.checked).toBe(true);
expect(panel.style.display).toBe('block');
});
it('should focus email input if no email provided', async () => {
const focusSpy = vi.spyOn(HTMLInputElement.prototype, 'focus');
await store.openTroubleshoot({
email: '',
includeUnraidApiLogs: true,
});
const emailInput = document.querySelector('#troubleshootEmail') as HTMLInputElement;
expect(focusSpy).toHaveBeenCalled();
expect(emailInput.value).toBe('');
focusSpy.mockRestore();
});
it('should handle errors during troubleshoot opening', async () => {
const testError = new Error('FeedbackButton failed');
mockFeedbackButton.mockRejectedValueOnce(testError);
const consoleSpy = vi.spyOn(console, 'error');
await store.openTroubleshoot({
email: 'test@example.com',
includeUnraidApiLogs: true,
});
expect(mockFeedbackButton).toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith('[openTroubleshoot]', testError);
consoleSpy.mockRestore();
});
});
});