test: create tests for stores (#1338)

This gets the original 3 component tests refactored to better follow the
Vue Testing Library philosophy and test behavior. This also adds a new
test file for the server store. Additional batches of tests will be
added in proceeding PR's.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Chores**  
- Streamlined internal code organization and improved maintenance
through refined import structures and cleanup of redundant files.

- **Tests**  
- Expanded and restructured automated tests across core components,
including new test files for `Auth`, `DownloadApiLogs`, and `KeyActions`
to ensure robust behavior.
- Enhanced test configuration and mock implementations for a more
reliable, consistent testing environment.
- Introduced best practices for testing Vue components and Pinia stores.

These updates optimize performance and stability behind the scenes
without altering the end-user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
This commit is contained in:
Michael Datelle
2025-04-09 11:57:11 -04:00
committed by GitHub
parent cd323acd49
commit 03be042410
33 changed files with 1407 additions and 1174 deletions
+47
View File
@@ -0,0 +1,47 @@
import { provideApolloClient } from '@vue/apollo-composable';
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core';
// Types for Apollo Client options
interface TestApolloClientOptions {
uri?: string;
mockData?: Record<string, unknown>;
}
// Single function to create Apollo clients
function createClient(options: TestApolloClientOptions = {}) {
const { uri = 'http://localhost/graphql', mockData = { data: {} } } = options;
return new ApolloClient({
link: createHttpLink({
uri,
credentials: 'include',
fetch: () => Promise.resolve(new Response(JSON.stringify(mockData))),
}),
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
},
},
});
}
// Default mock client
export const mockApolloClient = createClient();
// Helper function to provide the mock client
export function provideMockApolloClient() {
provideApolloClient(mockApolloClient);
return mockApolloClient;
}
// Create a customizable Apollo Client
export function createTestApolloClient(options: TestApolloClientOptions = {}) {
const client = createClient(options);
provideApolloClient(client);
return client;
}
+1
View File
@@ -0,0 +1 @@
import './request';
+15
View File
@@ -0,0 +1,15 @@
import { vi } from 'vitest';
// Mock any composables that might cause hanging
vi.mock('~/composables/services/request', () => {
return {
useRequest: vi.fn(() => ({
post: vi.fn(() => Promise.resolve({ data: {} })),
get: vi.fn(() => Promise.resolve({ data: {} })),
interceptors: {
request: { use: vi.fn() },
response: { use: vi.fn() },
},
})),
};
});
+43
View File
@@ -0,0 +1,43 @@
import { vi } from 'vitest';
import type { SendPayloads } from '@unraid/shared-callbacks';
// Mock shared callbacks
vi.mock('@unraid/shared-callbacks', () => ({
default: {
encrypt: (data: string) => data,
decrypt: (data: string) => data,
},
}));
// Mock implementation of the shared-callbacks module
export const mockSharedCallbacks = {
encrypt: (data: string, _key: string) => {
return data; // Simple mock that returns the input data
},
decrypt: (data: string, _key: string) => {
return data; // Simple mock that returns the input data
},
useCallback: ({ encryptionKey: _encryptionKey }: { encryptionKey: string }) => {
return {
send: (_payload: SendPayloads) => {
return Promise.resolve();
},
watcher: () => {
return null;
},
};
},
};
// Mock the crypto-js/aes module
vi.mock('crypto-js/aes.js', () => ({
default: {
encrypt: (data: string, _key: string) => {
return { toString: () => data };
},
decrypt: (data: string, _key: string) => {
return { toString: () => data };
},
},
}));
+13
View File
@@ -0,0 +1,13 @@
import { vi } from 'vitest';
// Mock specific problematic stores
vi.mock('~/store/errors', () => {
const useErrorsStore = vi.fn(() => ({
errors: { value: [] },
addError: vi.fn(),
clearErrors: vi.fn(),
removeError: vi.fn(),
}));
return { useErrorsStore };
});
+2
View File
@@ -0,0 +1,2 @@
import './errors';
import './server';
+16
View File
@@ -0,0 +1,16 @@
import { vi } from 'vitest';
// Mock the server store which is used by Auth component
vi.mock('~/store/server', () => {
return {
useServerStore: vi.fn(() => ({
authAction: 'authenticate',
stateData: { error: false, message: '' },
authToken: 'mock-token',
isAuthenticated: true,
authenticate: vi.fn(() => Promise.resolve()),
logout: vi.fn(),
resetAuth: vi.fn(),
})),
};
});
+44
View File
@@ -0,0 +1,44 @@
import { vi } from 'vitest';
// Mock @unraid/ui components and functions
const mockCn = (...args: unknown[]) => args.filter(Boolean).join(' ');
const MockBrandButton = {
name: 'BrandButton',
props: [
'class',
'disabled',
'external',
'href',
'icon',
'iconRight',
'iconRightHoverDisplay',
'text',
'title',
'download',
],
template: `
<component :is="props.href ? 'a' : 'button'"
:class="props.class"
:disabled="props.disabled"
:href="props.href"
:target="props.external ? '_blank' : undefined"
:rel="props.external ? 'noopener noreferrer' : undefined"
:title="props.title"
:download="props.download"
>
<span v-if="props.icon" class="icon">{{ props.icon }}</span>
{{ props.text || '' }} <slot />
<span v-if="props.iconRight" class="icon-right" :class="{ 'hover-only': props.iconRightHoverDisplay }">{{ props.iconRight }}</span>
</component>
`,
setup(props: Record<string, unknown>) {
return { props };
},
};
vi.mock('@unraid/ui', () => ({
cn: mockCn,
BrandButton: MockBrandButton,
// Add other UI components as needed
}));