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:
Eli Bosley
2025-07-08 10:05:39 -04:00
committed by GitHub
parent 7be8bc84d3
commit 345e83bfb0
109 changed files with 955 additions and 746 deletions

View File

@@ -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>;

View File

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

View File

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

View File

@@ -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')>();

View File

@@ -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';