mirror of
https://github.com/unraid/api.git
synced 2026-01-06 08:39:54 -06:00
feat: upgrade nuxt-custom-elements (#1461)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added new modal dialogs and UI components, including activation steps, OS update feedback, and expanded notification management. * Introduced a plugin to configure internationalization, state management, and Apollo client support in web components. * Added a new Log Viewer page with a streamlined interface for viewing logs. * **Improvements** * Centralized Pinia state management by consolidating all stores to use a shared global Pinia instance. * Simplified component templates by removing redundant internationalization host wrappers. * Enhanced ESLint configuration with stricter rules and global variable declarations. * Refined custom element build process to prevent jQuery conflicts and optimize minification. * Updated component imports and templates for consistent structure and maintainability. * Streamlined log viewer dropdowns using simplified select components with improved formatting. * Improved notification sidebar with filtering by importance and modular components. * Replaced legacy notification popups with new UI components and added automatic root session creation for localhost requests. * Updated OS version display and user profile UI with refined styling and component usage. * **Bug Fixes** * Fixed component tag capitalization and improved type annotations across components. * **Chores** * Updated development dependencies including ESLint plugins and build tools. * Removed deprecated log viewer patch class and cleaned up related test fixtures. * Removed unused imports and simplified Apollo client setup. * Cleaned up test mocks and removed obsolete i18n host component tests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210730229632804 --------- Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com> Co-authored-by: Zack Spear <zackspear@users.noreply.github.com>
This commit is contained in:
@@ -46,6 +46,15 @@ vi.mock('~/store/activationCode', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('~/components/Activation/store/activationCodeData', () => ({
|
||||
useActivationCodeDataStore: () => ({
|
||||
loading: ref(false),
|
||||
activationCode: ref(null),
|
||||
isFreshInstall: ref(false),
|
||||
partnerInfo: ref(null),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Auth Component', () => {
|
||||
let serverStore: ReturnType<typeof useServerStore>;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { nextTick } from 'vue';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import { Badge } from '@unraid/ui';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
@@ -26,6 +25,22 @@ vi.mock('@unraid/shared-callbacks', () => ({
|
||||
useCallback: vi.fn(() => ({ send: vi.fn(), watcher: vi.fn() })),
|
||||
}));
|
||||
|
||||
vi.mock('@vue/apollo-composable', () => ({
|
||||
useQuery: () => ({
|
||||
result: { value: {} },
|
||||
loading: { value: false },
|
||||
}),
|
||||
useLazyQuery: () => ({
|
||||
result: { value: {} },
|
||||
loading: { value: false },
|
||||
load: vi.fn(),
|
||||
refetch: vi.fn(),
|
||||
onResult: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
}),
|
||||
provideApolloClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('~/helpers/urls', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('~/helpers/urls')>();
|
||||
const mockReleaseNotesUrl = 'http://mock.release.notes/v';
|
||||
@@ -66,14 +81,6 @@ vi.mock('vue-i18n', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@unraid/ui', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@unraid/ui')>();
|
||||
return {
|
||||
...actual,
|
||||
Badge: actual.Badge,
|
||||
};
|
||||
});
|
||||
|
||||
describe('HeaderOsVersion', () => {
|
||||
let wrapper: VueWrapper<unknown>;
|
||||
let testingPinia: TestingPinia;
|
||||
@@ -102,7 +109,6 @@ describe('HeaderOsVersion', () => {
|
||||
wrapper = mount(HeaderOsVersion, {
|
||||
global: {
|
||||
plugins: [testingPinia],
|
||||
components: { Badge },
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -112,16 +118,13 @@ describe('HeaderOsVersion', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('renders OS version badge with correct link and no update status initially', () => {
|
||||
const versionBadgeLink = wrapper.find('a[title*="release notes"]');
|
||||
it('renders OS version link with correct URL and no update status initially', () => {
|
||||
const versionLink = wrapper.find('a[title*="release notes"]');
|
||||
|
||||
expect(versionBadgeLink.exists()).toBe(true);
|
||||
expect(versionBadgeLink.attributes('href')).toBe(`${testMockReleaseNotesUrl}6.12.0`);
|
||||
expect(versionLink.exists()).toBe(true);
|
||||
expect(versionLink.attributes('href')).toBe(`${testMockReleaseNotesUrl}6.12.0`);
|
||||
|
||||
const badge = versionBadgeLink.findComponent(Badge);
|
||||
|
||||
expect(badge.exists()).toBe(true);
|
||||
expect(badge.text()).toContain('6.12.0');
|
||||
expect(versionLink.text()).toContain('6.12.0');
|
||||
expect(findUpdateStatusComponent()).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* I18nHost Component Test Coverage
|
||||
*/
|
||||
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { I18nInjectionKey } from 'vue-i18n';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import _en_US from '~/locales/en_US.json';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { VueWrapper } from '@vue/test-utils';
|
||||
import type { Composer, I18n } from 'vue-i18n';
|
||||
|
||||
import I18nHost from '~/components/I18nHost.ce.vue';
|
||||
|
||||
const en_US: Record<string, string> = _en_US;
|
||||
|
||||
vi.mock('~/helpers/i18n-utils', () => ({
|
||||
createHtmlEntityDecoder: vi.fn(() => (text: string) => text),
|
||||
}));
|
||||
|
||||
const TestConsumerComponent = defineComponent({
|
||||
template: '<div>{{ i18n?.global.locale.value }}</div>',
|
||||
|
||||
setup() {
|
||||
const i18n = inject(I18nInjectionKey);
|
||||
return { i18n };
|
||||
},
|
||||
});
|
||||
|
||||
describe('I18nHost', () => {
|
||||
let wrapper: VueWrapper<unknown>;
|
||||
const originalWindowLocaleData = (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA;
|
||||
|
||||
beforeEach(() => {
|
||||
delete (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper?.unmount();
|
||||
|
||||
if (originalWindowLocaleData !== undefined) {
|
||||
(window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA = originalWindowLocaleData;
|
||||
} else {
|
||||
delete (window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA;
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('provides i18n instance with default locale when no window data exists', () => {
|
||||
wrapper = mount(I18nHost, {
|
||||
slots: {
|
||||
default: TestConsumerComponent,
|
||||
},
|
||||
});
|
||||
|
||||
const consumerWrapper = wrapper.findComponent(TestConsumerComponent);
|
||||
const providedI18n = consumerWrapper.vm.i18n as I18n<{
|
||||
message: typeof en_US;
|
||||
}>;
|
||||
|
||||
expect(providedI18n).toBeDefined();
|
||||
expect((providedI18n.global as Composer).locale.value).toBe('en_US');
|
||||
expect((providedI18n.global as Composer).fallbackLocale.value).toBe('en_US');
|
||||
|
||||
const messages = (providedI18n.global as Composer).messages.value as {
|
||||
en_US?: Record<string, string>;
|
||||
ja?: Record<string, string>;
|
||||
};
|
||||
|
||||
expect(messages.en_US?.['My Servers']).toBe(en_US['My Servers']);
|
||||
});
|
||||
|
||||
it('parses and provides i18n instance with locale from window.LOCALE_DATA', () => {
|
||||
const mockJaMessages = { 'test-key': 'テストキー' };
|
||||
const localeData = { ja: mockJaMessages };
|
||||
(window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA = encodeURIComponent(
|
||||
JSON.stringify(localeData)
|
||||
);
|
||||
|
||||
wrapper = mount(I18nHost, {
|
||||
slots: {
|
||||
default: TestConsumerComponent,
|
||||
},
|
||||
});
|
||||
|
||||
const consumerWrapper = wrapper.findComponent(TestConsumerComponent);
|
||||
const providedI18n = consumerWrapper.vm.i18n as I18n<{
|
||||
message: typeof en_US;
|
||||
}>;
|
||||
const messages = (providedI18n.global as Composer).messages.value as {
|
||||
en_US?: Record<string, string>;
|
||||
ja?: Record<string, string>;
|
||||
};
|
||||
|
||||
expect(providedI18n).toBeDefined();
|
||||
expect((providedI18n.global as Composer).locale.value).toBe('ja');
|
||||
expect((providedI18n.global as Composer).fallbackLocale.value).toBe('en_US');
|
||||
expect(messages.ja?.['test-key']).toBe(mockJaMessages['test-key']);
|
||||
expect(messages.en_US?.['My Servers']).toBe(en_US['My Servers']);
|
||||
});
|
||||
|
||||
it('handles invalid JSON in window.LOCALE_DATA gracefully', () => {
|
||||
(window as unknown as { LOCALE_DATA?: string }).LOCALE_DATA = 'invalid JSON string{%';
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
wrapper = mount(I18nHost, {
|
||||
slots: {
|
||||
default: TestConsumerComponent,
|
||||
},
|
||||
});
|
||||
|
||||
const consumerWrapper = wrapper.findComponent(TestConsumerComponent);
|
||||
const providedI18n = consumerWrapper.vm.i18n as I18n<{
|
||||
message: typeof en_US;
|
||||
}>;
|
||||
const messages = (providedI18n.global as Composer).messages.value as {
|
||||
en_US?: Record<string, string>;
|
||||
ja?: Record<string, string>;
|
||||
};
|
||||
|
||||
expect(providedI18n).toBeDefined();
|
||||
expect((providedI18n.global as Composer).locale.value).toBe('en_US');
|
||||
expect((providedI18n.global as Composer).fallbackLocale.value).toBe('en_US');
|
||||
expect(errorSpy).toHaveBeenCalledOnce();
|
||||
expect(errorSpy).toHaveBeenCalledWith('[I18nHost] error parsing messages', expect.any(Error));
|
||||
expect(messages.en_US?.['My Servers']).toBe(en_US['My Servers']);
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,22 @@ vi.mock('@unraid/shared-callbacks', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@vue/apollo-composable', () => ({
|
||||
useQuery: () => ({
|
||||
result: { value: {} },
|
||||
loading: { value: false },
|
||||
}),
|
||||
useLazyQuery: () => ({
|
||||
result: { value: {} },
|
||||
loading: { value: false },
|
||||
load: vi.fn(),
|
||||
refetch: vi.fn(),
|
||||
onResult: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
}),
|
||||
provideApolloClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@unraid/ui', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@unraid/ui')>();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { VueWrapper } from '@vue/test-utils';
|
||||
import type { Server, ServerconnectPluginInstalled, ServerState } from '~/types/server';
|
||||
import type { Pinia } from 'pinia';
|
||||
import type { MaybeRef } from '@vueuse/core';
|
||||
|
||||
import UserProfile from '~/components/UserProfile.ce.vue';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
Reference in New Issue
Block a user