mirror of
https://github.com/unraid/api.git
synced 2026-01-04 07:29:48 -06:00
- Refactored teleport container management to be lazily created, improving performance by avoiding unnecessary DOM manipulations. - Updated `useTeleport` to dynamically determine the correct teleport target based on mounted components. - Removed the `ensureTeleportContainer` import from various components, streamlining the mounting process. - Adjusted the dropdown menu component to utilize a computed property for teleport target management. - Enhanced the component registry to support a unified app architecture, replacing legacy mounting functions with a more efficient approach. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Support bundles may include the GraphQL API log when present. * OS version data loads lazily when the header dropdown first opens. * Many UI components now load on demand via a unified mounting approach. * **Bug Fixes** * Dropdowns and modals consistently stack above other UI elements. * Server status layout fixes improve small-screen alignment. * **Breaking Changes** * Teleport/mounting APIs and public teleport helper were consolidated/removed; integrations may need update. * **Tests** * Extensive new unit tests added for mounting, teleport, modals, and REST log handling. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
212 lines
7.3 KiB
TypeScript
212 lines
7.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
// Mock all the component imports
|
|
vi.mock('~/components/Auth.standalone.vue', () => ({
|
|
default: { name: 'MockAuth', template: '<div>Auth</div>' },
|
|
}));
|
|
vi.mock('~/components/ConnectSettings/ConnectSettings.standalone.vue', () => ({
|
|
default: { name: 'MockConnectSettings', template: '<div>ConnectSettings</div>' },
|
|
}));
|
|
vi.mock('~/components/DownloadApiLogs.standalone.vue', () => ({
|
|
default: { name: 'MockDownloadApiLogs', template: '<div>DownloadApiLogs</div>' },
|
|
}));
|
|
vi.mock('~/components/HeaderOsVersion.standalone.vue', () => ({
|
|
default: { name: 'MockHeaderOsVersion', template: '<div>HeaderOsVersion</div>' },
|
|
}));
|
|
vi.mock('~/components/Modals.standalone.vue', () => ({
|
|
default: { name: 'MockModals', template: '<div>Modals</div>' },
|
|
}));
|
|
vi.mock('~/components/UserProfile.standalone.vue', () => ({
|
|
default: { name: 'MockUserProfile', template: '<div>UserProfile</div>' },
|
|
}));
|
|
vi.mock('~/components/UpdateOs.standalone.vue', () => ({
|
|
default: { name: 'MockUpdateOs', template: '<div>UpdateOs</div>' },
|
|
}));
|
|
vi.mock('~/components/DowngradeOs.standalone.vue', () => ({
|
|
default: { name: 'MockDowngradeOs', template: '<div>DowngradeOs</div>' },
|
|
}));
|
|
vi.mock('~/components/Registration.standalone.vue', () => ({
|
|
default: { name: 'MockRegistration', template: '<div>Registration</div>' },
|
|
}));
|
|
vi.mock('~/components/WanIpCheck.standalone.vue', () => ({
|
|
default: { name: 'MockWanIpCheck', template: '<div>WanIpCheck</div>' },
|
|
}));
|
|
vi.mock('~/components/Activation/WelcomeModal.standalone.vue', () => ({
|
|
default: { name: 'MockWelcomeModal', template: '<div>WelcomeModal</div>' },
|
|
}));
|
|
vi.mock('~/components/SsoButton.standalone.vue', () => ({
|
|
default: { name: 'MockSsoButton', template: '<div>SsoButton</div>' },
|
|
}));
|
|
vi.mock('~/components/Logs/LogViewer.standalone.vue', () => ({
|
|
default: { name: 'MockLogViewer', template: '<div>LogViewer</div>' },
|
|
}));
|
|
vi.mock('~/components/ThemeSwitcher.standalone.vue', () => ({
|
|
default: { name: 'MockThemeSwitcher', template: '<div>ThemeSwitcher</div>' },
|
|
}));
|
|
vi.mock('~/components/ApiKeyPage.standalone.vue', () => ({
|
|
default: { name: 'MockApiKeyPage', template: '<div>ApiKeyPage</div>' },
|
|
}));
|
|
vi.mock('~/components/DevModalTest.standalone.vue', () => ({
|
|
default: { name: 'MockDevModalTest', template: '<div>DevModalTest</div>' },
|
|
}));
|
|
vi.mock('~/components/ApiKeyAuthorize.standalone.vue', () => ({
|
|
default: { name: 'MockApiKeyAuthorize', template: '<div>ApiKeyAuthorize</div>' },
|
|
}));
|
|
vi.mock('~/components/UnraidToaster.vue', () => ({
|
|
default: { name: 'MockUnraidToaster', template: '<div>UnraidToaster</div>' },
|
|
}));
|
|
|
|
// Mock mount-engine module
|
|
const mockAutoMountAllComponents = vi.fn();
|
|
const mockMountUnifiedApp = vi.fn();
|
|
|
|
vi.mock('~/components/Wrapper/mount-engine', () => ({
|
|
autoMountAllComponents: mockAutoMountAllComponents,
|
|
mountUnifiedApp: mockMountUnifiedApp,
|
|
}));
|
|
|
|
// Mock theme initializer
|
|
const mockInitializeTheme = vi.fn(() => Promise.resolve());
|
|
vi.mock('~/store/themeInitializer', () => ({
|
|
initializeTheme: mockInitializeTheme,
|
|
isThemeReady: vi.fn(() => true),
|
|
resetThemeInitialization: vi.fn(),
|
|
}));
|
|
|
|
// Mock globalPinia
|
|
vi.mock('~/store/globalPinia', () => ({
|
|
globalPinia: { state: {} },
|
|
}));
|
|
|
|
// Mock apollo client
|
|
const mockApolloClient = {
|
|
query: vi.fn(),
|
|
mutate: vi.fn(),
|
|
};
|
|
vi.mock('~/helpers/create-apollo-client', () => ({
|
|
client: mockApolloClient,
|
|
}));
|
|
|
|
// Mock @vue/apollo-composable
|
|
const mockProvideApolloClient = vi.fn();
|
|
vi.mock('@vue/apollo-composable', () => ({
|
|
provideApolloClient: mockProvideApolloClient,
|
|
}));
|
|
|
|
// Mock graphql
|
|
const mockParse = vi.fn();
|
|
vi.mock('graphql', () => ({
|
|
parse: mockParse,
|
|
}));
|
|
|
|
// Mock @unraid/ui
|
|
vi.mock('@unraid/ui', () => ({}));
|
|
|
|
describe('component-registry', () => {
|
|
beforeEach(() => {
|
|
// Reset module cache to ensure fresh imports
|
|
vi.resetModules();
|
|
|
|
// Reset all mocks
|
|
vi.clearAllMocks();
|
|
|
|
// Use Vitest's unstubAllGlobals to clean up any global stubs from previous tests
|
|
vi.unstubAllGlobals();
|
|
|
|
// Mock document methods
|
|
vi.spyOn(document.head, 'appendChild').mockImplementation(() => document.createElement('style'));
|
|
vi.spyOn(document, 'addEventListener').mockImplementation(() => {});
|
|
|
|
// Clear DOM
|
|
document.head.innerHTML = '';
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.resetModules();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe('initialization', () => {
|
|
it('should set up Apollo client globally', async () => {
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
expect(window.apolloClient).toBe(mockApolloClient);
|
|
expect(window.graphqlParse).toBe(mockParse);
|
|
expect(window.gql).toBe(mockParse);
|
|
expect(mockProvideApolloClient).toHaveBeenCalledWith(mockApolloClient);
|
|
});
|
|
|
|
it('should initialize theme once', async () => {
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
expect(mockInitializeTheme).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should mount unified app with components', async () => {
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
// The unified app architecture no longer requires teleport container setup per component
|
|
// Instead it uses a unified approach
|
|
expect(mockAutoMountAllComponents).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('component auto-mounting', () => {
|
|
it('should auto-mount components when DOM elements exist', async () => {
|
|
// Create DOM elements for components to mount to
|
|
const authElement = document.createElement('div');
|
|
authElement.setAttribute('id', 'unraid-auth');
|
|
document.body.appendChild(authElement);
|
|
|
|
const modalElement = document.createElement('div');
|
|
modalElement.setAttribute('id', 'modals');
|
|
document.body.appendChild(modalElement);
|
|
|
|
// Clear previous calls
|
|
mockAutoMountAllComponents.mockClear();
|
|
|
|
// Import auto-mount which will trigger auto-mounting
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
// Auto-mount should be called when DOM is ready
|
|
expect(mockAutoMountAllComponents).toHaveBeenCalled();
|
|
|
|
// Clean up
|
|
document.body.removeChild(authElement);
|
|
document.body.removeChild(modalElement);
|
|
});
|
|
});
|
|
|
|
describe('global exports', () => {
|
|
it('should expose utility functions globally', async () => {
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
// With unified app architecture, these are exposed instead:
|
|
expect(window.apolloClient).toBe(mockApolloClient);
|
|
expect(window.gql).toBe(mockParse);
|
|
expect(window.graphqlParse).toBe(mockParse);
|
|
// The unified app itself is exposed via window.__unifiedApp after mounting
|
|
});
|
|
|
|
it('should not expose legacy mount functions', async () => {
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
// These functions are no longer exposed in the unified app architecture
|
|
expect(window.mountVueApp).toBeUndefined();
|
|
expect(window.getMountedApp).toBeUndefined();
|
|
expect(window.autoMountComponent).toBeUndefined();
|
|
});
|
|
|
|
it('should expose apollo client and graphql utilities', async () => {
|
|
await import('~/components/Wrapper/auto-mount');
|
|
|
|
// Check that Apollo client and GraphQL utilities are exposed
|
|
expect(window.apolloClient).toBeDefined();
|
|
expect(typeof window.gql).toBe('function');
|
|
expect(typeof window.graphqlParse).toBe('function');
|
|
});
|
|
});
|
|
});
|