Files
api/web/__test__/components/UpdateOs.test.ts
Eli Bosley 88087d5201 feat: mount vue apps, not web components (#1639)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Standalone web bundle with auto-mount utilities and a self-contained
test page.
* New responsive modal components for consistent mobile/desktop dialogs.
  * Header actions to copy OS/API versions.

* **Improvements**
* Refreshed UI styles (muted borders), accessibility and animation
refinements.
  * Theming updates and Tailwind v4–aligned, component-scoped styles.
  * Runtime GraphQL endpoint override and CSRF header support.

* **Bug Fixes**
* Safer network fetching and improved manifest/asset loading with
duplicate protection.

* **Tests/Chores**
* Parallel plugin tests, new extractor test suite, and updated
build/test scripts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-03 15:42:21 -04:00

236 lines
8.3 KiB
TypeScript

/**
* UpdateOs Component Test Coverage
*/
import { nextTick, ref } from 'vue';
import { mount } from '@vue/test-utils';
import { createTestingPinia } from '@pinia/testing';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import UpdateOs from '~/components/UpdateOs.ce.vue';
vi.mock('@unraid/ui', () => ({
PageContainer: { template: '<div><slot /></div>' },
BrandLoading: { template: '<div data-testid="brand-loading-mock">Loading...</div>' },
}));
const mockAccountStore = {
updateOs: vi.fn(),
};
vi.mock('~/store/account', () => ({
useAccountStore: () => mockAccountStore,
}));
const mockRebootType = ref('');
const mockSetRebootVersion = vi.fn();
const mockServerStore = {
rebootType: mockRebootType,
setRebootVersion: mockSetRebootVersion,
};
vi.mock('~/store/server', () => ({
useServerStore: () => mockServerStore,
}));
const mockT = vi.fn((key: string) => key);
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: mockT,
}),
}));
const mockLocation = {
pathname: '/some/other/path',
};
vi.stubGlobal('location', mockLocation);
vi.mock('~/helpers/urls', () => ({
WEBGUI_TOOLS_UPDATE: { pathname: '/Tools/Update' },
}));
const UpdateOsStatusStub = {
template: '<div data-testid="update-os-status">Status</div>',
props: ['showUpdateCheck', 'title', 'subtitle', 't'],
};
const UpdateOsThirdPartyDriversStub = {
template: '<div data-testid="third-party-drivers">Third Party</div>',
props: ['t'],
};
describe('UpdateOs.ce.vue', () => {
beforeEach(() => {
vi.clearAllMocks();
mockRebootType.value = '';
mockSetRebootVersion.mockClear();
mockAccountStore.updateOs.mockClear();
mockT.mockClear().mockImplementation((key: string) => key);
mockLocation.pathname = '/some/other/path';
});
it('calls setRebootVersion with prop value on mount', () => {
const testVersion = '6.12.0';
mount(UpdateOs, {
props: { rebootVersion: testVersion },
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
// Rely on @unraid/ui mock for PageContainer
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
expect(mockSetRebootVersion).toHaveBeenCalledTimes(1);
expect(mockSetRebootVersion).toHaveBeenCalledWith(testVersion);
});
it('calls setRebootVersion with empty string if prop not provided', () => {
mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
// Rely on @unraid/ui mock for PageContainer
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
expect(mockSetRebootVersion).toHaveBeenCalledTimes(1);
expect(mockSetRebootVersion).toHaveBeenCalledWith(''); // Default prop value
});
describe('Initial Rendering and onBeforeMount Logic', () => {
it('shows loader and calls updateOs when path matches and rebootType is empty', async () => {
mockLocation.pathname = '/Tools/Update';
mockRebootType.value = '';
const wrapper = mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
// Rely on @unraid/ui mock for PageContainer & BrandLoading
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
await nextTick();
expect(mockAccountStore.updateOs).toHaveBeenCalledTimes(1);
expect(mockAccountStore.updateOs).toHaveBeenCalledWith(true);
// Since v-show is used, both elements exist in DOM but visibility is toggled
expect(wrapper.find('[data-testid="brand-loading-mock"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="brand-loading-mock"]').isVisible()).toBe(true);
expect(wrapper.find('[data-testid="update-os-status"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="update-os-status"]').isVisible()).toBe(false);
});
it('shows status and does not call updateOs when path does not match', async () => {
mockLocation.pathname = '/some/other/path';
mockRebootType.value = '';
const wrapper = mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
// Rely on @unraid/ui mock for PageContainer & BrandLoading
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
await nextTick();
expect(mockAccountStore.updateOs).not.toHaveBeenCalled();
// Since v-show is used, both elements exist in DOM but visibility is toggled
expect(wrapper.find('[data-testid="brand-loading-mock"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="brand-loading-mock"]').isVisible()).toBe(false);
expect(wrapper.find('[data-testid="update-os-status"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="update-os-status"]').isVisible()).toBe(true);
});
it('shows status and does not call updateOs when path matches but rebootType is not empty', async () => {
mockLocation.pathname = '/Tools/Update';
mockRebootType.value = 'downgrade';
const wrapper = mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
// Rely on @unraid/ui mock for PageContainer & BrandLoading
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
await nextTick();
expect(mockAccountStore.updateOs).not.toHaveBeenCalled();
// Since v-show is used, both elements exist in DOM but visibility is toggled
expect(wrapper.find('[data-testid="brand-loading-mock"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="brand-loading-mock"]').isVisible()).toBe(false);
expect(wrapper.find('[data-testid="update-os-status"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="update-os-status"]').isVisible()).toBe(true);
});
});
describe('Rendering based on rebootType', () => {
it('passes correct subtitle when rebootType is "downgrade"', () => {
mockRebootType.value = 'downgrade';
const wrapper = mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
const statusStub = wrapper.findComponent(UpdateOsStatusStub);
expect(statusStub.exists()).toBe(true);
expect(statusStub.props('subtitle')).toBe(
'Please finish the initiated downgrade to enable updates.'
);
expect(wrapper.find('[data-testid="third-party-drivers"]').exists()).toBe(false);
});
it('renders UpdateOsThirdPartyDrivers when rebootType is "thirdPartyDriversDownloading"', () => {
mockRebootType.value = 'thirdPartyDriversDownloading';
const wrapper = mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
expect(wrapper.find('[data-testid="update-os-status"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="third-party-drivers"]').exists()).toBe(true);
expect(wrapper.findComponent(UpdateOsStatusStub).props('subtitle')).toBe('');
});
it('renders only UpdateOsStatus for other rebootType values', () => {
mockRebootType.value = 'update';
const wrapper = mount(UpdateOs, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
stubs: {
UpdateOsStatus: UpdateOsStatusStub,
UpdateOsThirdPartyDrivers: UpdateOsThirdPartyDriversStub,
},
},
});
expect(wrapper.find('[data-testid="update-os-status"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="third-party-drivers"]').exists()).toBe(false);
expect(wrapper.findComponent(UpdateOsStatusStub).props('subtitle')).toBe('');
});
});
});