Files
api/web/__test__/components/Modals.test.ts
Eli Bosley c855caa9b2 fix: improve vue mount speed by 10x
- 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>
2025-09-15 11:24:31 -04:00

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);
});
});