mirror of
https://github.com/unraid/api.git
synced 2026-02-10 18:09:02 -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>
233 lines
7.4 KiB
TypeScript
233 lines
7.4 KiB
TypeScript
import { nextTick } from 'vue';
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import { mount } from '@vue/test-utils';
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { VueWrapper } from '@vue/test-utils';
|
|
import type { Pinia } from 'pinia';
|
|
|
|
import Modals from '~/components/Modals.standalone.vue';
|
|
import { useCallbackActionsStore } from '~/store/callbackActions';
|
|
import { useTrialStore } from '~/store/trial';
|
|
import { useUpdateOsStore } from '~/store/updateOs';
|
|
|
|
// Mock child components
|
|
vi.mock('~/components/Activation/ActivationModal.vue', () => ({
|
|
default: {
|
|
name: 'ActivationModal',
|
|
props: ['t'],
|
|
template: '<div>ActivationModal</div>',
|
|
},
|
|
}));
|
|
|
|
vi.mock('~/components/UpdateOs/ChangelogModal.vue', () => ({
|
|
default: {
|
|
name: 'UpdateOsChangelogModal',
|
|
props: ['t', 'open'],
|
|
template: '<div v-if="open">ChangelogModal</div>',
|
|
},
|
|
}));
|
|
|
|
vi.mock('~/components/UpdateOs/CheckUpdateResponseModal.vue', () => ({
|
|
default: {
|
|
name: 'UpdateOsCheckUpdateResponseModal',
|
|
props: ['t', 'open'],
|
|
template: '<div v-if="open">CheckUpdateResponseModal</div>',
|
|
},
|
|
}));
|
|
|
|
vi.mock('~/components/UserProfile/CallbackFeedback.vue', () => ({
|
|
default: {
|
|
name: 'UpcCallbackFeedback',
|
|
props: ['t', 'open'],
|
|
template: '<div v-if="open">CallbackFeedback</div>',
|
|
},
|
|
}));
|
|
|
|
vi.mock('~/components/UserProfile/Trial.vue', () => ({
|
|
default: {
|
|
name: 'UpcTrial',
|
|
props: ['t', 'open'],
|
|
template: '<div v-if="open">Trial</div>',
|
|
},
|
|
}));
|
|
|
|
vi.mock('vue-i18n', () => ({
|
|
useI18n: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}));
|
|
|
|
describe('Modals.standalone.vue', () => {
|
|
let wrapper: VueWrapper;
|
|
let pinia: Pinia;
|
|
|
|
beforeEach(() => {
|
|
pinia = createPinia();
|
|
setActivePinia(pinia);
|
|
|
|
wrapper = mount(Modals, {
|
|
global: {
|
|
plugins: [pinia],
|
|
},
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
wrapper?.unmount();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should render modals container with correct id and ref', () => {
|
|
const modalsDiv = wrapper.find('#modals');
|
|
expect(modalsDiv.exists()).toBe(true);
|
|
expect(modalsDiv.attributes('class')).toContain('relative');
|
|
expect(modalsDiv.attributes('class')).toContain('z-[999999]');
|
|
});
|
|
|
|
it('should render all modal components', () => {
|
|
expect(wrapper.findComponent({ name: 'UpcCallbackFeedback' }).exists()).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'UpcTrial' }).exists()).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'UpdateOsCheckUpdateResponseModal' }).exists()).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'UpdateOsChangelogModal' }).exists()).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'ActivationModal' }).exists()).toBe(true);
|
|
});
|
|
|
|
it('should pass correct props to CallbackFeedback based on store state', async () => {
|
|
const callbackStore = useCallbackActionsStore();
|
|
callbackStore.callbackStatus = 'loading';
|
|
|
|
await nextTick();
|
|
|
|
const callbackFeedback = wrapper.findComponent({ name: 'UpcCallbackFeedback' });
|
|
expect(callbackFeedback.props('open')).toBe(true);
|
|
|
|
callbackStore.callbackStatus = 'ready';
|
|
await nextTick();
|
|
expect(callbackFeedback.props('open')).toBe(false);
|
|
});
|
|
|
|
it('should pass correct props to Trial modal based on store state', async () => {
|
|
const trialStore = useTrialStore();
|
|
// trialModalVisible is computed based on trialStatus
|
|
trialStore.trialStatus = 'trialStart';
|
|
|
|
await nextTick();
|
|
|
|
const trialModal = wrapper.findComponent({ name: 'UpcTrial' });
|
|
expect(trialModal.props('open')).toBe(true);
|
|
|
|
trialStore.trialStatus = 'ready';
|
|
await nextTick();
|
|
expect(trialModal.props('open')).toBe(false);
|
|
});
|
|
|
|
it('should pass correct props to UpdateOs modal based on store state', async () => {
|
|
const updateOsStore = useUpdateOsStore();
|
|
updateOsStore.setModalOpen(true);
|
|
|
|
await nextTick();
|
|
|
|
const updateOsModal = wrapper.findComponent({ name: 'UpdateOsCheckUpdateResponseModal' });
|
|
expect(updateOsModal.props('open')).toBe(true);
|
|
|
|
updateOsStore.setModalOpen(false);
|
|
await nextTick();
|
|
expect(updateOsModal.props('open')).toBe(false);
|
|
});
|
|
|
|
it('should pass correct props to Changelog modal based on store state', async () => {
|
|
const updateOsStore = useUpdateOsStore();
|
|
// changelogModalVisible is computed based on releaseForUpdate
|
|
updateOsStore.setReleaseForUpdate({
|
|
version: '6.13.0',
|
|
name: 'Unraid 6.13.0',
|
|
date: '2024-01-01',
|
|
isNewer: true,
|
|
isEligible: true,
|
|
changelog: null,
|
|
sha256: null,
|
|
});
|
|
|
|
await nextTick();
|
|
|
|
const changelogModal = wrapper.findComponent({ name: 'UpdateOsChangelogModal' });
|
|
expect(changelogModal.props('open')).toBe(true);
|
|
|
|
updateOsStore.setReleaseForUpdate(null);
|
|
await nextTick();
|
|
expect(changelogModal.props('open')).toBe(false);
|
|
});
|
|
|
|
it('should pass translation function to all modals', () => {
|
|
const components = [
|
|
'UpcCallbackFeedback',
|
|
'UpcTrial',
|
|
'UpdateOsCheckUpdateResponseModal',
|
|
'UpdateOsChangelogModal',
|
|
'ActivationModal',
|
|
];
|
|
|
|
components.forEach((componentName) => {
|
|
const component = wrapper.findComponent({ name: componentName });
|
|
expect(component.props('t')).toBeDefined();
|
|
expect(typeof component.props('t')).toBe('function');
|
|
});
|
|
});
|
|
|
|
it('should use computed properties for reactive store access', async () => {
|
|
// Test that computed properties react to store changes
|
|
const callbackStore = useCallbackActionsStore();
|
|
const trialStore = useTrialStore();
|
|
const updateOsStore = useUpdateOsStore();
|
|
|
|
// Initially all should be closed/default
|
|
expect(wrapper.findComponent({ name: 'UpcCallbackFeedback' }).props('open')).toBe(false);
|
|
expect(wrapper.findComponent({ name: 'UpcTrial' }).props('open')).toBe(false);
|
|
expect(wrapper.findComponent({ name: 'UpdateOsCheckUpdateResponseModal' }).props('open')).toBe(
|
|
false
|
|
);
|
|
expect(wrapper.findComponent({ name: 'UpdateOsChangelogModal' }).props('open')).toBe(false);
|
|
|
|
// Update all stores using proper methods
|
|
callbackStore.callbackStatus = 'loading';
|
|
trialStore.trialStatus = 'trialStart';
|
|
updateOsStore.setModalOpen(true);
|
|
updateOsStore.setReleaseForUpdate({
|
|
version: '6.13.0',
|
|
name: 'Unraid 6.13.0',
|
|
date: '2024-01-01',
|
|
isNewer: true,
|
|
isEligible: true,
|
|
changelog: null,
|
|
sha256: null,
|
|
});
|
|
|
|
await nextTick();
|
|
|
|
// All should be open now
|
|
expect(wrapper.findComponent({ name: 'UpcCallbackFeedback' }).props('open')).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'UpcTrial' }).props('open')).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'UpdateOsCheckUpdateResponseModal' }).props('open')).toBe(true);
|
|
expect(wrapper.findComponent({ name: 'UpdateOsChangelogModal' }).props('open')).toBe(true);
|
|
});
|
|
|
|
it('should render modals container even when all modals are closed', () => {
|
|
const callbackStore = useCallbackActionsStore();
|
|
const trialStore = useTrialStore();
|
|
const updateOsStore = useUpdateOsStore();
|
|
|
|
// Set all modals to closed state
|
|
callbackStore.callbackStatus = 'ready';
|
|
trialStore.trialStatus = 'ready';
|
|
updateOsStore.setModalOpen(false);
|
|
updateOsStore.setReleaseForUpdate(null);
|
|
|
|
const modalsDiv = wrapper.find('#modals');
|
|
expect(modalsDiv.exists()).toBe(true);
|
|
// Container should still exist
|
|
expect(wrapper.findComponent({ name: 'ActivationModal' }).exists()).toBe(true);
|
|
});
|
|
});
|