mirror of
https://github.com/unraid/api.git
synced 2025-12-30 13:09:52 -06:00
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the ability to create Docker folders and set folder children via new GraphQL mutations in the Docker organizer. * Enhanced organizer management with pure functions for folder creation and child assignment, ensuring immutability. * **Bug Fixes** * Improved validation to prevent empty or invalid folder entries in the organizer structure. * **Tests** * Added comprehensive tests for folder creation, child assignment, and organizer resolution, including edge cases and immutability checks. * Updated test guidelines to focus on observable behavior and error handling best practices. * **Documentation** * Expanded testing best practices in project documentation. * **Chores** * Updated ignore rules for local configuration files. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
245 lines
7.6 KiB
Plaintext
245 lines
7.6 KiB
Plaintext
---
|
|
description:
|
|
globs: **/*.test.ts,**/__test__/components/**/*.ts,**/__test__/store/**/*.ts,**/__test__/mocks/**/*.ts
|
|
alwaysApply: false
|
|
---
|
|
|
|
## General Testing Best Practices
|
|
- **Error Testing:** Use `.rejects.toThrow()` without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
|
|
- **Focus on Behavior:** Test what the code does, not implementation details like exact error message wording
|
|
|
|
## Vue Component Testing Best Practices
|
|
- This is a Nuxt.js app but we are testing with vitest outside of the Nuxt environment
|
|
- Nuxt is currently set to auto import so some vue files may need compute or ref imported
|
|
- Use pnpm when running termical commands and stay within the web directory.
|
|
- The directory for tests is located under `web/__test__` when running test just run `pnpm test`
|
|
|
|
### Setup
|
|
- Use `mount` from Vue Test Utils for component testing
|
|
- Stub complex child components that aren't the focus of the test
|
|
- Mock external dependencies and services
|
|
|
|
```typescript
|
|
import { mount } from '@vue/test-utils';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { createTestingPinia } from '@pinia/testing'
|
|
import { useSomeStore } from '@/stores/myStore'
|
|
import YourComponent from '~/components/YourComponent.vue';
|
|
|
|
// Mock dependencies
|
|
vi.mock('~/helpers/someHelper', () => ({
|
|
SOME_CONSTANT: 'mocked-value',
|
|
}));
|
|
|
|
describe('YourComponent', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renders correctly', () => {
|
|
const wrapper = mount(YourComponent, {
|
|
global: {
|
|
plugins: [createTestingPinia()],
|
|
stubs: {
|
|
// Stub child components when needed
|
|
ChildComponent: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
const store = useSomeStore() // uses the testing pinia!
|
|
// state can be directly manipulated
|
|
store.name = 'my new name'
|
|
|
|
// actions are stubbed by default, meaning they don't execute their code by default.
|
|
// See below to customize this behavior.
|
|
store.someAction()
|
|
|
|
expect(store.someAction).toHaveBeenCalledTimes(1)
|
|
|
|
// Assertions on components
|
|
expect(wrapper.text()).toContain('Expected content');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Testing Patterns
|
|
- Test component behavior and output, not implementation details
|
|
- Verify that the expected elements are rendered
|
|
- Test component interactions (clicks, inputs, etc.)
|
|
- Check for expected prop handling and event emissions
|
|
- Use `createTestingPinia()` for mocking stores in components
|
|
|
|
### Finding Elements
|
|
- Use semantic queries like `find('button')` or `find('[data-test="id"]')` but prefer not to use data test ID's
|
|
- Find components with `findComponent(ComponentName)`
|
|
- Use `findAll` to check for multiple elements
|
|
|
|
### Assertions
|
|
- Assert on rendered text content with `wrapper.text()`
|
|
- Assert on element attributes with `element.attributes()`
|
|
- Verify element existence with `expect(element.exists()).toBe(true)`
|
|
- Check component state through rendered output
|
|
|
|
### Component Interaction
|
|
- Trigger events with `await element.trigger('click')`
|
|
- Set input values with `await input.setValue('value')`
|
|
- Test emitted events with `wrapper.emitted()`
|
|
|
|
### Mocking
|
|
- Mock external services and API calls
|
|
- Prefer not using mocks whenever possible
|
|
- Use `vi.mock()` for module-level mocks
|
|
- Specify return values for component methods with `vi.spyOn()`
|
|
- Reset mocks between tests with `vi.clearAllMocks()`
|
|
- Frequently used mocks are stored under `web/test/mocks`
|
|
|
|
### Async Testing
|
|
- Use `await nextTick()` for DOM updates
|
|
- Use `flushPromises()` for more complex promise chains
|
|
- Always await async operations before making assertions
|
|
|
|
## Store Testing with Pinia
|
|
|
|
### Basic Setup
|
|
- When testing Store files use `createPinia` and `setActivePinia`
|
|
|
|
```typescript
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { useYourStore } from '~/store/your-store';
|
|
|
|
// Mock declarations must be at top level due to hoisting
|
|
const mockDependencyFn = vi.fn();
|
|
|
|
// Module mocks must use factory functions
|
|
vi.mock('~/store/dependency', () => ({
|
|
useDependencyStore: () => ({
|
|
someMethod: mockDependencyFn,
|
|
someProperty: 'mockValue'
|
|
})
|
|
}));
|
|
|
|
describe('Your Store', () => {
|
|
let store: ReturnType<typeof useYourStore>;
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia());
|
|
store = useYourStore();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.resetAllMocks();
|
|
});
|
|
|
|
it('tests some action', () => {
|
|
store.someAction();
|
|
expect(mockDependencyFn).toHaveBeenCalled();
|
|
});
|
|
});
|
|
```
|
|
|
|
### Important Guidelines
|
|
1. **Store Initialization**
|
|
- Use `createPinia()` instead of `createTestingPinia()` for most cases
|
|
- Only use `createTestingPinia` if you specifically need its testing features
|
|
- Let stores initialize with their natural default state instead of forcing initial state
|
|
- Do not mock the store we're actually testing in the test file. That's why we're using `createPinia()`
|
|
|
|
2. **Vue Reactivity**
|
|
- Ensure Vue reactivity imports are added to original store files as they may be missing because Nuxt auto import was turned on
|
|
- Don't rely on Nuxt auto-imports in tests
|
|
|
|
```typescript
|
|
// Required in store files, even with Nuxt auto-imports
|
|
import { computed, ref, watchEffect } from 'vue';
|
|
```
|
|
|
|
3. **Mocking Best Practices**
|
|
- Place all mock declarations at the top level
|
|
- Use factory functions for module mocks to avoid hoisting issues
|
|
|
|
```typescript
|
|
// ❌ Wrong - will cause hoisting issues
|
|
const mockFn = vi.fn();
|
|
vi.mock('module', () => ({ method: mockFn }));
|
|
|
|
// ✅ Correct - using factory function
|
|
vi.mock('module', () => {
|
|
const mockFn = vi.fn();
|
|
return { method: mockFn };
|
|
});
|
|
```
|
|
|
|
4. **Testing Actions**
|
|
- Test action side effects and state changes
|
|
- Verify actions are called with correct parameters
|
|
- Mock external dependencies appropriately
|
|
|
|
```typescript
|
|
it('should handle action correctly', () => {
|
|
store.yourAction();
|
|
expect(mockDependencyFn).toHaveBeenCalledWith(
|
|
expectedArg1,
|
|
expectedArg2
|
|
);
|
|
expect(store.someState).toBe(expectedValue);
|
|
});
|
|
```
|
|
|
|
5. **Common Pitfalls**
|
|
- Don't mix mock declarations and module mocks incorrectly
|
|
- Avoid relying on Nuxt's auto-imports in test environment
|
|
- Clear mocks between tests to ensure isolation
|
|
- Remember that `vi.mock()` calls are hoisted
|
|
|
|
### Testing State & Getters
|
|
- Test computed properties by accessing them directly
|
|
- Verify state changes after actions
|
|
- Test getter dependencies are properly mocked
|
|
|
|
```typescript
|
|
it('computes derived state correctly', () => {
|
|
store.setState('new value');
|
|
expect(store.computedValue).toBe('expected result');
|
|
});
|
|
```
|
|
|
|
### Testing Complex Interactions
|
|
- Test store interactions with other stores
|
|
- Verify proper error handling
|
|
- Test async operations completely
|
|
|
|
```typescript
|
|
it('handles async operations', async () => {
|
|
const promise = store.asyncAction();
|
|
expect(store.status).toBe('loading');
|
|
await promise;
|
|
expect(store.status).toBe('success');
|
|
});
|
|
```
|
|
|
|
### Testing Actions
|
|
- Verify actions are called with the right parameters
|
|
- Test action side effects if not stubbed
|
|
- Override specific action implementations when needed
|
|
|
|
```typescript
|
|
// Test action calls
|
|
store.yourAction(params);
|
|
expect(store.yourAction).toHaveBeenCalledWith(params);
|
|
|
|
// Test with real implementation
|
|
const pinia = createTestingPinia({
|
|
createSpy: vi.fn,
|
|
stubActions: false,
|
|
});
|
|
```
|
|
|
|
### Testing State & Getters
|
|
- Set initial state for focused testing
|
|
- Test computed properties by accessing them directly
|
|
- Verify state changes by updating the store
|
|
|