diff --git a/src/components/__tests__/StatusBar.test.js b/src/components/__tests__/StatusBar.test.js
index 349c9402..016f136c 100644
--- a/src/components/__tests__/StatusBar.test.js
+++ b/src/components/__tests__/StatusBar.test.js
@@ -226,9 +226,7 @@ describe('StatusBar.vue - Servers indicator', () => {
test('shows Servers indicator with green dot when no issues', () => {
const wrapper = mountStatusBar();
expect(wrapper.text()).toContain('Servers');
- const serversDots = wrapper.findAll(
- '.bg-\\[var\\(--status-online\\)\\]'
- );
+ const serversDots = wrapper.findAll('.bg-status-online');
expect(serversDots.length).toBeGreaterThan(0);
expect(wrapper.find('.bg-\\[\\#e6a23c\\]').exists()).toBe(false);
});
diff --git a/src/components/dialogs/GroupDialog/__tests__/GroupDialogPhotosTab.test.js b/src/components/dialogs/GroupDialog/__tests__/GroupDialogPhotosTab.test.js
index d70b9241..fb6740da 100644
--- a/src/components/dialogs/GroupDialog/__tests__/GroupDialogPhotosTab.test.js
+++ b/src/components/dialogs/GroupDialog/__tests__/GroupDialogPhotosTab.test.js
@@ -6,10 +6,11 @@ import { mount } from '@vue/test-utils';
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
+ t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key),
+ locale: require('vue').ref('en')
}),
createI18n: () => ({
- global: { t: (key) => key },
+ global: { t: (key) => key , locale: require('vue').ref('en') },
install: vi.fn()
})
}));
diff --git a/src/components/dialogs/GroupDialog/__tests__/GroupDialogPostsTab.test.js b/src/components/dialogs/GroupDialog/__tests__/GroupDialogPostsTab.test.js
index 47445fb6..21946a0e 100644
--- a/src/components/dialogs/GroupDialog/__tests__/GroupDialogPostsTab.test.js
+++ b/src/components/dialogs/GroupDialog/__tests__/GroupDialogPostsTab.test.js
@@ -4,15 +4,20 @@ import { mount } from '@vue/test-utils';
// ─── Mocks ───────────────────────────────────────────────────────────
-vi.mock('vue-i18n', () => ({
- useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
- }),
- createI18n: () => ({
- global: { t: (key) => key },
- install: vi.fn()
- })
-}));
+vi.mock('vue-i18n', () => {
+ const { ref } = require('vue');
+ return {
+ useI18n: () => ({
+ t: (key, params) =>
+ params ? `${key}:${JSON.stringify(params)}` : key,
+ locale: ref('en')
+ }),
+ createI18n: () => ({
+ global: { t: (key) => key, locale: ref('en') },
+ install: vi.fn()
+ })
+ };
+});
vi.mock('../../../../plugin/router', () => {
const { ref } = require('vue');
diff --git a/src/components/dialogs/GroupDialog/__tests__/GroupModerationBulkActions.test.js b/src/components/dialogs/GroupDialog/__tests__/GroupModerationBulkActions.test.js
index 00406a41..a4ab902f 100644
--- a/src/components/dialogs/GroupDialog/__tests__/GroupModerationBulkActions.test.js
+++ b/src/components/dialogs/GroupDialog/__tests__/GroupModerationBulkActions.test.js
@@ -4,9 +4,11 @@ import { mount } from '@vue/test-utils';
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- }),
+ ,
+ locale: require('vue').ref('en')
+ }),
createI18n: () => ({
- global: { t: (key) => key },
+ global: { t: (key) => key , locale: require('vue').ref('en') },
install: vi.fn()
})
}));
diff --git a/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js b/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js
index 2fa9594f..b935d5af 100644
--- a/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js
+++ b/src/components/dialogs/GroupDialog/__tests__/useGroupMembers.test.js
@@ -80,9 +80,11 @@ vi.mock('../../../../service/request', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- }),
+ ,
+ locale: require('vue').ref('en')
+ }),
createI18n: () => ({
- global: { t: (key) => key },
+ global: { t: (key) => key , locale: require('vue').ref('en') },
install: vi.fn()
})
}));
diff --git a/src/components/dialogs/GroupDialog/__tests__/useGroupModerationData.test.js b/src/components/dialogs/GroupDialog/__tests__/useGroupModerationData.test.js
index 354e477b..d9e9d8b9 100644
--- a/src/components/dialogs/GroupDialog/__tests__/useGroupModerationData.test.js
+++ b/src/components/dialogs/GroupDialog/__tests__/useGroupModerationData.test.js
@@ -5,9 +5,11 @@ vi.mock('vue-sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- }),
+ ,
+ locale: require('vue').ref('en')
+ }),
createI18n: () => ({
- global: { t: (key) => key },
+ global: { t: (key) => key , locale: require('vue').ref('en') },
install: vi.fn()
})
}));
diff --git a/src/components/dialogs/UserDialog/UserDialog.vue b/src/components/dialogs/UserDialog/UserDialog.vue
index 4462cac1..428fb404 100644
--- a/src/components/dialogs/UserDialog/UserDialog.vue
+++ b/src/components/dialogs/UserDialog/UserDialog.vue
@@ -19,538 +19,7 @@
:unmount-on-hide="false"
@update:modelValue="userDialogTabClick">
-
-
-
-
-
-
-
-
-
-
-
-
-
![]()
-
-
-
- {{
- t('dialog.user.info.instance_creator')
- }}
-
-
-
-
-
-
-
![]()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{
- t('dialog.user.info.note')
- }}
-
{{ userDialog.note }}
-
-
-
-
-
{{
- t('dialog.user.info.memo')
- }}
-
{{ userDialog.memo }}
-
-
-
-
-
- {{
- userDialog.id !== currentUser.id &&
- userDialog.ref.profilePicOverride &&
- userDialog.ref.currentAvatarImageUrl
- ? t('dialog.user.info.avatar_info_last_seen')
- : t('dialog.user.info.avatar_info')
- }}
-
-
-
-
-
-
-
-
-
-
{{
- t('dialog.user.info.represented_group')
- }}
-
-
-
- 👑
-
- ({{ userDialog.representedGroup.memberCount }})
-
-
-
-
-
-
-
-
-
{{
- t('dialog.user.info.bio')
- }}
-
{{ bioCache.translated || userDialog.ref.bio || '-' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dialog.user.info.last_seen') }}
-
- {{
- formatDateFilter(userDialog.lastSeen, 'long')
- }}
-
-
-
-
-
-
-
- {{ t('dialog.user.info.join_count') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('dialog.user.info.time_together') }}
-
- -
- {{
- timeToText(userDialog.timeSpent)
- }}
-
-
-
-
-
-
-
-
- {{ t('dialog.user.info.play_time') }}
-
- -
- {{
- timeToText(userDialog.timeSpent)
- }}
-
-
-
-
-
-
-
- {{ formatDateFilter(userOnlineForTimestamp(userDialog), 'short') }}
-
-
-
- {{ t('dialog.user.info.online_for') }}
-
-
- {{ t('dialog.user.info.offline_for') }}
-
- {{ userOnlineFor(userDialog.ref) }}
-
-
-
-
-
-
- {{ t('dialog.user.info.last_login') }}
- {{ formatDateFilter(userDialog.ref.last_login, 'long') }}
-
- {{ t('dialog.user.info.last_activity') }}
- {{ formatDateFilter(userDialog.ref.last_activity, 'long') }}
-
-
- {{
- t('dialog.user.info.last_activity')
- }}
- {{
- timeToText(Date.now() - Date.parse(userDialog.ref.last_activity))
- }}
- -
-
-
-
-
-
- {{
- t('dialog.user.info.date_joined')
- }}
-
-
-
-
-
-
-
- {{ ref.type }}: {{ formatDateFilter(ref.created_at, 'long') }}
-
-
-
-
- {{ t('dialog.user.info.unfriended') }}
-
-
- {{ t('dialog.user.info.friended') }}
-
- {{
- formatDateFilter(userDialog.dateFriended, 'long')
- }}
-
-
-
-
-
-
- {{
- t('dialog.user.info.avatar_cloning')
- }}
- {{
- t('dialog.user.info.avatar_cloning_allow')
- }}
- {{
- t('dialog.user.info.avatar_cloning_deny')
- }}
-
-
-
-
- {{
- t('dialog.user.info.booping')
- }}
- {{
- t('dialog.user.info.avatar_cloning_allow')
- }}
- {{
- t('dialog.user.info.avatar_cloning_deny')
- }}
-
-
-
-
- {{
- t('dialog.user.info.show_mutual_friends')
- }}
- {{
- t('dialog.user.info.avatar_cloning_allow')
- }}
- {{
- t('dialog.user.info.avatar_cloning_deny')
- }}
-
-
-
-
- {{
- t('dialog.user.info.show_discord_connections')
- }}
- {{
- t('dialog.user.info.avatar_cloning_allow')
- }}
- {{
- t('dialog.user.info.avatar_cloning_deny')
- }}
-
-
-
-
-
-
- {{
- t('dialog.user.info.avatar_cloning')
- }}
- {{
- t('dialog.user.info.avatar_cloning_allow')
- }}
- {{
- t('dialog.user.info.avatar_cloning_deny')
- }}
-
-
-
-
-
- {{
- t('view.profile.profile.vrchat_credits')
- }}
- {{
- vrchatCredit ?? t('view.profile.profile.refresh')
- }}
-
-
-
-
- {{
- t('dialog.user.info.home_location')
- }}
-
-
-
-
-
-
-
-
- {{
- t('dialog.user.info.id')
- }}
-
- {{ userDialog.id }}
-
-
-
-
-
-
-
- {{ t('dialog.user.info.copy_id') }}
-
-
- {{ t('dialog.user.info.copy_url') }}
-
-
- {{ t('dialog.user.info.copy_display_name') }}
-
-
-
-
-
-
-
-
+
@@ -597,31 +66,18 @@
-
diff --git a/src/components/dialogs/UserDialog/UserDialogInfoTab.vue b/src/components/dialogs/UserDialog/UserDialogInfoTab.vue
new file mode 100644
index 00000000..a1d89948
--- /dev/null
+++ b/src/components/dialogs/UserDialog/UserDialogInfoTab.vue
@@ -0,0 +1,712 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+ {{ t('dialog.user.info.instance_creator') }}
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ t('dialog.user.info.note') }}
+
{{ userDialog.note }}
+
+
+
+
+
{{ t('dialog.user.info.memo') }}
+
{{ userDialog.memo }}
+
+
+
+
+
+ {{
+ userDialog.id !== currentUser.id &&
+ userDialog.ref.profilePicOverride &&
+ userDialog.ref.currentAvatarImageUrl
+ ? t('dialog.user.info.avatar_info_last_seen')
+ : t('dialog.user.info.avatar_info')
+ }}
+
+
+
+
+
+
+
+
+
+
{{
+ t('dialog.user.info.represented_group')
+ }}
+
+
+
+ 👑
+
+ ({{ userDialog.representedGroup.memberCount }})
+
+
+
-
+
+
+
+
+
{{ t('dialog.user.info.bio') }}
+
{{ bioCache.translated || userDialog.ref.bio || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('dialog.user.info.last_seen') }}
+
+ {{ formatDateFilter(userDialog.lastSeen, 'long') }}
+
+
+
+
+
+
+
+ {{ t('dialog.user.info.join_count') }}
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+ {{ t('dialog.user.info.time_together') }}
+
+ -
+ {{ timeToText(userDialog.timeSpent) }}
+
+
+
+
+
+
+
+
+ {{ t('dialog.user.info.play_time') }}
+
+ -
+ {{ timeToText(userDialog.timeSpent) }}
+
+
+
+
+
+
+
+ {{ formatDateFilter(userOnlineForTimestamp(userDialog), 'short') }}
+
+
+
+ {{ t('dialog.user.info.online_for') }}
+
+
+ {{ t('dialog.user.info.offline_for') }}
+
+ {{ userOnlineFor(userDialog.ref) }}
+
+
+
+
+
+
+ {{ t('dialog.user.info.last_login') }}
+ {{ formatDateFilter(userDialog.ref.last_login, 'long') }}
+
+ {{ t('dialog.user.info.last_activity') }}
+ {{ formatDateFilter(userDialog.ref.last_activity, 'long') }}
+
+
+ {{
+ t('dialog.user.info.last_activity')
+ }}
+ {{
+ timeToText(Date.now() - Date.parse(userDialog.ref.last_activity))
+ }}
+ -
+
+
+
+
+
+ {{ t('dialog.user.info.date_joined') }}
+
+
+
+
+
+
+
+ {{ ref.type }}: {{ formatDateFilter(ref.created_at, 'long') }}
+
+
+
+
+ {{ t('dialog.user.info.unfriended') }}
+
+
+ {{ t('dialog.user.info.friended') }}
+
+ {{ formatDateFilter(userDialog.dateFriended, 'long') }}
+
+
+
+
+
+
+ {{
+ t('dialog.user.info.avatar_cloning')
+ }}
+ {{
+ t('dialog.user.info.avatar_cloning_allow')
+ }}
+ {{ t('dialog.user.info.avatar_cloning_deny') }}
+
+
+
+
+ {{ t('dialog.user.info.booping') }}
+ {{
+ t('dialog.user.info.avatar_cloning_allow')
+ }}
+ {{ t('dialog.user.info.avatar_cloning_deny') }}
+
+
+
+
+ {{
+ t('dialog.user.info.show_mutual_friends')
+ }}
+ {{
+ t('dialog.user.info.avatar_cloning_allow')
+ }}
+ {{ t('dialog.user.info.avatar_cloning_deny') }}
+
+
+
+
+ {{
+ t('dialog.user.info.show_discord_connections')
+ }}
+ {{
+ t('dialog.user.info.avatar_cloning_allow')
+ }}
+ {{ t('dialog.user.info.avatar_cloning_deny') }}
+
+
+
+
+
+
+ {{
+ t('dialog.user.info.avatar_cloning')
+ }}
+ {{
+ t('dialog.user.info.avatar_cloning_allow')
+ }}
+ {{ t('dialog.user.info.avatar_cloning_deny') }}
+
+
+
+
+
+ {{
+ t('view.profile.profile.vrchat_credits')
+ }}
+ {{ vrchatCredit ?? t('view.profile.profile.refresh') }}
+
+
+
+
+ {{ t('dialog.user.info.home_location') }}
+
+
+
+
+
+
+
+
+ {{ t('dialog.user.info.id') }}
+
+ {{ userDialog.id }}
+
+
+
+
+
+
+
+ {{ t('dialog.user.info.copy_id') }}
+
+
+ {{ t('dialog.user.info.copy_url') }}
+
+
+ {{ t('dialog.user.info.copy_display_name') }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/dialogs/UserDialog/__tests__/UserDialogAvatarsTab.test.js b/src/components/dialogs/UserDialog/__tests__/UserDialogAvatarsTab.test.js
index f722ad2a..9e555e39 100644
--- a/src/components/dialogs/UserDialog/__tests__/UserDialogAvatarsTab.test.js
+++ b/src/components/dialogs/UserDialog/__tests__/UserDialogAvatarsTab.test.js
@@ -4,15 +4,20 @@ import { mount } from '@vue/test-utils';
// ─── Mocks (must be before any imports that use them) ────────────────
-vi.mock('vue-i18n', () => ({
- useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
- }),
- createI18n: () => ({
- global: { t: (key) => key },
- install: vi.fn()
- })
-}));
+vi.mock('vue-i18n', () => {
+ const { ref } = require('vue');
+ return {
+ useI18n: () => ({
+ t: (key, params) =>
+ params ? `${key}:${JSON.stringify(params)}` : key,
+ locale: ref('en')
+ }),
+ createI18n: () => ({
+ global: { t: (key) => key, locale: ref('en') },
+ install: vi.fn()
+ })
+ };
+});
vi.mock('../../../../plugin/router', () => {
const { ref } = require('vue');
diff --git a/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js b/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js
new file mode 100644
index 00000000..33adde3d
--- /dev/null
+++ b/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js
@@ -0,0 +1,241 @@
+import { beforeEach, describe, expect, test, vi } from 'vitest';
+import { createTestingPinia } from '@pinia/testing';
+import { flushPromises, shallowMount } from '@vue/test-utils';
+
+vi.mock('vue-i18n', () => ({
+ useI18n: () => {
+ const { ref } = require('vue');
+ return {
+ t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key),
+ locale: ref('en')
+ };
+ },
+ createI18n: () => ({
+ global: { t: (key) => key },
+ install: vi.fn()
+ })
+}));
+
+vi.mock('../../../../plugin/router', () => {
+ const { ref } = require('vue');
+ return {
+ router: {
+ beforeEach: vi.fn(),
+ push: vi.fn(),
+ replace: vi.fn(),
+ currentRoute: ref({ path: '/', name: '', meta: {} }),
+ isReady: vi.fn().mockResolvedValue(true)
+ },
+ initRouter: vi.fn()
+ };
+});
+
+vi.mock('vue-router', async (importOriginal) => {
+ const actual = await importOriginal();
+ const { ref } = require('vue');
+ return {
+ ...actual,
+ useRouter: vi.fn(() => ({
+ push: vi.fn(),
+ replace: vi.fn(),
+ currentRoute: ref({ path: '/', name: '', meta: {} })
+ }))
+ };
+});
+
+vi.mock('../../../../plugin/interopApi', () => ({ initInteropApi: vi.fn() }));
+vi.mock('../../../../service/database', () => ({
+ database: new Proxy(
+ {},
+ {
+ get: (_target, prop) => {
+ if (prop === '__esModule') return false;
+ return vi.fn().mockResolvedValue(null);
+ }
+ }
+ )
+}));
+
+vi.mock('../../../../service/config', () => ({
+ default: {
+ init: vi.fn(),
+ getString: vi.fn().mockImplementation((_k, d) => d ?? '{}'),
+ setString: vi.fn(),
+ getBool: vi.fn().mockImplementation((_k, d) => d ?? false),
+ setBool: vi.fn(),
+ getInt: vi.fn().mockImplementation((_k, d) => d ?? 0),
+ setInt: vi.fn(),
+ getFloat: vi.fn().mockImplementation((_k, d) => d ?? 0),
+ setFloat: vi.fn(),
+ getObject: vi.fn().mockReturnValue(null),
+ setObject: vi.fn(),
+ getArray: vi.fn().mockReturnValue([]),
+ setArray: vi.fn(),
+ remove: vi.fn()
+ }
+}));
+
+vi.mock('../../../../service/jsonStorage', () => ({ default: vi.fn() }));
+vi.mock('../../../../service/watchState', () => ({
+ watchState: { isLoggedIn: false }
+}));
+vi.mock('../../../../service/request', () => ({
+ request: vi.fn().mockResolvedValue({ json: {} }),
+ processBulk: vi.fn(),
+ buildRequestInit: vi.fn(),
+ parseResponse: vi.fn(),
+ shouldIgnoreError: vi.fn(),
+ $throw: vi.fn(),
+ failedGetRequests: new Map()
+}));
+
+import UserDialogInfoTab from '../UserDialogInfoTab.vue';
+import { miscRequest } from '../../../../api';
+import {
+ useAdvancedSettingsStore,
+ useAppearanceSettingsStore,
+ useLocationStore,
+ useModalStore,
+ useUserStore
+} from '../../../../stores';
+
+/**
+ *
+ * @param overrides
+ */
+function mountComponent(overrides = {}) {
+ const pinia = createTestingPinia({
+ stubActions: true
+ });
+
+ const appearanceSettingsStore = useAppearanceSettingsStore(pinia);
+ appearanceSettingsStore.hideUserNotes = false;
+ appearanceSettingsStore.hideUserMemos = false;
+
+ const advancedSettingsStore = useAdvancedSettingsStore(pinia);
+ advancedSettingsStore.bioLanguage = 'en';
+ advancedSettingsStore.translationApi = '';
+ advancedSettingsStore.translationApiType = 'google';
+ advancedSettingsStore.translateText = vi.fn().mockResolvedValue('');
+
+ const userStore = useUserStore(pinia);
+ userStore.userDialog = {
+ id: 'usr_target',
+ friend: {
+ state: 'online',
+ ref: {
+ location: 'wrld_test:123'
+ }
+ },
+ ref: {
+ id: 'usr_target',
+ location: 'wrld_test:123',
+ travelingToLocation: '',
+ profilePicOverride: '',
+ currentAvatarImageUrl: '',
+ currentAvatarTags: [],
+ bio: '',
+ bioLinks: [],
+ state: 'online',
+ $online_for: 1000,
+ last_login: '2025-01-01T00:00:00.000Z',
+ last_activity: '2025-01-01T00:00:00.000Z',
+ date_joined: '2020-01-01',
+ allowAvatarCopying: true,
+ displayName: 'Target'
+ },
+ $location: {
+ tag: 'wrld_test:123',
+ shortName: 'Test',
+ userId: '',
+ user: null
+ },
+ instance: {
+ ref: {},
+ friendCount: 0
+ },
+ users: [
+ {
+ id: 'usr_friend_1',
+ displayName: 'Friend A',
+ $userColour: '#ffffff',
+ location: 'traveling',
+ $travelingToTime: Date.now(),
+ $location_at: Date.now()
+ }
+ ],
+ note: '',
+ memo: '',
+ isRepresentedGroupLoading: false,
+ representedGroup: null,
+ lastSeen: '2025-01-01T00:00:00.000Z',
+ joinCount: 0,
+ timeSpent: 0,
+ dateFriendedInfo: [],
+ unFriended: false,
+ dateFriended: '2025-01-01T00:00:00.000Z',
+ $homeLocationName: '',
+ ...overrides.userDialog
+ };
+
+ userStore.currentUser = {
+ id: 'usr_me',
+ allowAvatarCopying: true,
+ isBoopingEnabled: true,
+ hasSharedConnectionsOptOut: false,
+ hasDiscordFriendsOptOut: false,
+ homeLocation: '',
+ ...overrides.currentUser
+ };
+
+ const locationStore = useLocationStore(pinia);
+ locationStore.lastLocation = {
+ location: 'wrld_test:123'
+ };
+
+ const modalStore = useModalStore(pinia);
+ modalStore.confirm = vi.fn().mockResolvedValue({ ok: false });
+
+ return shallowMount(UserDialogInfoTab, {
+ global: {
+ plugins: [pinia],
+ stubs: {
+ Location: true,
+ Timer: true,
+ TooltipWrapper: true,
+ AvatarInfo: true
+ }
+ }
+ });
+}
+
+describe('UserDialogInfoTab.vue', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('unit behavior', () => {
+ test('onTabActivated fetches VRChat credits only once after first success', async () => {
+ const creditsSpy = vi
+ .spyOn(miscRequest, 'getVRChatCredits')
+ .mockResolvedValue({ json: { balance: 42 } });
+ const wrapper = mountComponent();
+
+ wrapper.vm.onTabActivated();
+ await flushPromises();
+ wrapper.vm.onTabActivated();
+ await flushPromises();
+
+ expect(creditsSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('dom rendering', () => {
+ test('renders imported InstanceActionBar and Spinner components when conditions are met', () => {
+ const wrapper = mountComponent();
+
+ expect(wrapper.find('instance-action-bar-stub').exists()).toBe(true);
+ expect(wrapper.find('spinner-stub').exists()).toBe(true);
+ });
+ });
+});
diff --git a/src/components/dialogs/UserDialog/__tests__/UserDialogMutualFriendsTab.test.js b/src/components/dialogs/UserDialog/__tests__/UserDialogMutualFriendsTab.test.js
index 318fdeaa..72652ec5 100644
--- a/src/components/dialogs/UserDialog/__tests__/UserDialogMutualFriendsTab.test.js
+++ b/src/components/dialogs/UserDialog/__tests__/UserDialogMutualFriendsTab.test.js
@@ -6,10 +6,11 @@ import { mount } from '@vue/test-utils';
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
+ t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key),
+ locale: require('vue').ref('en')
}),
createI18n: () => ({
- global: { t: (key) => key },
+ global: { t: (key) => key , locale: require('vue').ref('en') },
install: vi.fn()
})
}));
diff --git a/src/components/dialogs/UserDialog/__tests__/UserDialogWorldsTab.test.js b/src/components/dialogs/UserDialog/__tests__/UserDialogWorldsTab.test.js
index 87db8584..bdf7ced9 100644
--- a/src/components/dialogs/UserDialog/__tests__/UserDialogWorldsTab.test.js
+++ b/src/components/dialogs/UserDialog/__tests__/UserDialogWorldsTab.test.js
@@ -4,15 +4,20 @@ import { mount } from '@vue/test-utils';
// ─── Mocks ───────────────────────────────────────────────────────────
-vi.mock('vue-i18n', () => ({
- useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
- }),
- createI18n: () => ({
- global: { t: (key) => key },
- install: vi.fn()
- })
-}));
+vi.mock('vue-i18n', () => {
+ const { ref } = require('vue');
+ return {
+ useI18n: () => ({
+ t: (key, params) =>
+ params ? `${key}:${JSON.stringify(params)}` : key,
+ locale: ref('en')
+ }),
+ createI18n: () => ({
+ global: { t: (key) => key, locale: ref('en') },
+ install: vi.fn()
+ })
+ };
+});
vi.mock('../../../../plugin/router', () => {
const { ref } = require('vue');
diff --git a/src/composables/__tests__/useImageCropper.test.js b/src/composables/__tests__/useImageCropper.test.js
index 5deb2d74..61d5aa93 100644
--- a/src/composables/__tests__/useImageCropper.test.js
+++ b/src/composables/__tests__/useImageCropper.test.js
@@ -5,7 +5,9 @@ vi.mock('vue-sonner', () => ({
}));
vi.mock('vue-i18n', () => ({
- useI18n: () => ({ t: (key) => key })
+ useI18n: () => ({ t: (key) => key ,
+ locale: require('vue').ref('en')
+ })
}));
import {
diff --git a/src/stores/__tests__/vrcStatus.test.js b/src/stores/__tests__/vrcStatus.test.js
index 85acfe2c..c7a26c24 100644
--- a/src/stores/__tests__/vrcStatus.test.js
+++ b/src/stores/__tests__/vrcStatus.test.js
@@ -39,7 +39,9 @@ vi.mock('../../shared/utils', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
/**
diff --git a/src/stores/__tests__/vrcxUpdater.test.js b/src/stores/__tests__/vrcxUpdater.test.js
index 1e0e13a7..ea4b05f7 100644
--- a/src/stores/__tests__/vrcxUpdater.test.js
+++ b/src/stores/__tests__/vrcxUpdater.test.js
@@ -29,7 +29,9 @@ vi.mock('vue-sonner', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
function flushPromises() {
diff --git a/src/stores/favorite.js b/src/stores/favorite.js
index c7acd405..0e6fc709 100644
--- a/src/stores/favorite.js
+++ b/src/stores/favorite.js
@@ -213,8 +213,9 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param list
- * @param selectionRef
+ * @param {Array} list
+ * @param {object} selectionRef
+ * @returns {void}
*/
function syncFavoriteSelection(list, selectionRef) {
if (!Array.isArray(list)) {
@@ -263,7 +264,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
);
/**
- *
+ * @returns {void}
*/
function getCachedFavoriteGroupsByTypeName() {
const group = {};
@@ -286,7 +287,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param objectId
+ * @param {string} objectId
+ * @returns {object | undefined}
*/
function getCachedFavoritesByObjectId(objectId) {
return cachedFavoritesByObjectId.get(objectId);
@@ -294,7 +296,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param args
+ * @param {object} args
+ * @returns {void}
*/
function handleFavoriteAdd(args) {
handleFavorite({
@@ -330,7 +333,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param args
+ * @param {object} args
+ * @returns {void}
*/
function handleFavorite(args) {
args.ref = applyFavoriteCached(args.json);
@@ -353,7 +357,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param objectId
+ * @param {string} objectId
+ * @returns {void}
*/
function handleFavoriteDelete(objectId) {
const ref = getCachedFavoritesByObjectId(objectId);
@@ -365,7 +370,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param args
+ * @param {object} args
+ * @returns {void}
*/
function handleFavoriteGroup(args) {
args.ref = applyFavoriteGroup(args.json);
@@ -373,7 +379,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param args
+ * @param {object} args
+ * @returns {void}
*/
function handleFavoriteGroupClear(args) {
const key = `${args.params.type}:${args.params.group}`;
@@ -387,7 +394,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param args
+ * @param {object} args
+ * @returns {void}
*/
function handleFavoriteWorldList(args) {
for (const json of args.json) {
@@ -400,7 +408,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param args
+ * @param {object} args
*/
function handleFavoriteAvatarList(args) {
for (const json of args.json) {
@@ -413,7 +421,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param ref
+ * @param {object} ref
+ * @returns {void}
*/
function handleFavoriteAtDelete(ref) {
const favorite = state.favoriteObjects.get(ref.favoriteId);
@@ -583,7 +592,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
}
/**
- *
+ * @returns {void}
*/
function refreshFavoriteGroups() {
if (isFavoriteGroupLoading.value) {
@@ -813,8 +822,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param json
- * @returns {any}
+ * @param {object} json
+ * @returns {object}
*/
function applyFavoriteGroup(json) {
let ref = cachedFavoriteGroups.value[json.id];
@@ -829,8 +838,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param json
- * @returns {any}
+ * @param {object} json
+ * @returns {object}
*/
function applyFavoriteCached(json) {
let ref = cachedFavorites.get(json.id);
@@ -866,7 +875,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param tag
+ * @param {string} tag
+ * @returns {void}
*/
async function refreshFavoriteAvatars(tag) {
const params = {
@@ -879,7 +889,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
}
/**
- *
+ * @returns {void}
*/
function refreshFavoriteItems() {
const types = {
@@ -929,21 +939,21 @@ export const useFavoriteStore = defineStore('Favorite', () => {
}
/**
- *
+ * @returns {void}
*/
function showWorldImportDialog() {
worldImportDialogVisible.value = true;
}
/**
- *
+ * @returns {void}
*/
function showAvatarImportDialog() {
avatarImportDialogVisible.value = true;
}
/**
- *
+ * @returns {void}
*/
function showFriendImportDialog() {
friendImportDialogVisible.value = true;
@@ -1104,7 +1114,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
/**
*
- * @param objectId
+ * @param {string} objectId
+ * @returns {void}
*/
function updateFavoriteDialog(objectId) {
const D = favoriteDialog.value;
@@ -1199,7 +1210,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
}
/**
- *
+ * @returns {void}
*/
function sortLocalAvatarFavorites() {
if (!appearanceSettingsStore.sortFavorites) {
@@ -1389,7 +1400,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
}
/**
- *
+ * @returns {void}
*/
function sortLocalWorldFavorites() {
if (!appearanceSettingsStore.sortFavorites) {
@@ -1450,6 +1461,10 @@ export const useFavoriteStore = defineStore('Favorite', () => {
});
await new Promise((resolve) => setTimeout(resolve, 500));
} catch (err) {
+ console.error(
+ `Failed to fetch avatar ${favorite.id}:`,
+ err
+ );
result.invalid++;
result.invalidIds.push(favorite.id);
}
diff --git a/src/views/Favorites/components/__tests__/FavoritesWorldItem.test.js b/src/views/Favorites/components/__tests__/FavoritesWorldItem.test.js
index b6bd52dd..1259ce8d 100644
--- a/src/views/Favorites/components/__tests__/FavoritesWorldItem.test.js
+++ b/src/views/Favorites/components/__tests__/FavoritesWorldItem.test.js
@@ -18,7 +18,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('@/components/ui/context-menu', () => ({
diff --git a/src/views/FriendList/__tests__/FriendList.test.js b/src/views/FriendList/__tests__/FriendList.test.js
index 3c2e0338..ca7efd93 100644
--- a/src/views/FriendList/__tests__/FriendList.test.js
+++ b/src/views/FriendList/__tests__/FriendList.test.js
@@ -46,7 +46,8 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key) => key
+ t: (key) => key,
+ locale: require('vue').ref('en')
})
}));
diff --git a/src/views/FriendLog/__tests__/FriendLog.test.js b/src/views/FriendLog/__tests__/FriendLog.test.js
index fb511563..c6d085de 100644
--- a/src/views/FriendLog/__tests__/FriendLog.test.js
+++ b/src/views/FriendLog/__tests__/FriendLog.test.js
@@ -34,7 +34,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../stores', () => ({
diff --git a/src/views/FriendsLocations/__tests__/FriendsLocations.test.js b/src/views/FriendsLocations/__tests__/FriendsLocations.test.js
index f5ad3f40..ce3b8980 100644
--- a/src/views/FriendsLocations/__tests__/FriendsLocations.test.js
+++ b/src/views/FriendsLocations/__tests__/FriendsLocations.test.js
@@ -44,7 +44,8 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key) => key
+ t: (key) => key,
+ locale: require('vue').ref('en')
})
}));
diff --git a/src/views/Login/Dialog/__tests__/LoginSettingsDialog.test.js b/src/views/Login/Dialog/__tests__/LoginSettingsDialog.test.js
index 75152ff7..9010766d 100644
--- a/src/views/Login/Dialog/__tests__/LoginSettingsDialog.test.js
+++ b/src/views/Login/Dialog/__tests__/LoginSettingsDialog.test.js
@@ -19,7 +19,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../../stores', () => ({
diff --git a/src/views/Moderation/__tests__/Moderation.test.js b/src/views/Moderation/__tests__/Moderation.test.js
index 2246b954..4034e50c 100644
--- a/src/views/Moderation/__tests__/Moderation.test.js
+++ b/src/views/Moderation/__tests__/Moderation.test.js
@@ -32,7 +32,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../stores', () => ({
diff --git a/src/views/PlayerList/__tests__/PlayerList.test.js b/src/views/PlayerList/__tests__/PlayerList.test.js
index 654b8d38..0b6c67de 100644
--- a/src/views/PlayerList/__tests__/PlayerList.test.js
+++ b/src/views/PlayerList/__tests__/PlayerList.test.js
@@ -28,7 +28,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../stores', () => ({
diff --git a/src/views/Settings/dialogs/__tests__/ChangelogDialog.test.js b/src/views/Settings/dialogs/__tests__/ChangelogDialog.test.js
index 67525833..821ebe93 100644
--- a/src/views/Settings/dialogs/__tests__/ChangelogDialog.test.js
+++ b/src/views/Settings/dialogs/__tests__/ChangelogDialog.test.js
@@ -20,7 +20,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../../stores', () => ({
diff --git a/src/views/Settings/dialogs/__tests__/LaunchOptionsDialog.test.js b/src/views/Settings/dialogs/__tests__/LaunchOptionsDialog.test.js
index 07b25017..3afcf84c 100644
--- a/src/views/Settings/dialogs/__tests__/LaunchOptionsDialog.test.js
+++ b/src/views/Settings/dialogs/__tests__/LaunchOptionsDialog.test.js
@@ -27,7 +27,9 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../../stores', () => ({
diff --git a/src/views/Settings/dialogs/__tests__/TranslationApiDialog.test.js b/src/views/Settings/dialogs/__tests__/TranslationApiDialog.test.js
index a7c5c3da..a7bc1a29 100644
--- a/src/views/Settings/dialogs/__tests__/TranslationApiDialog.test.js
+++ b/src/views/Settings/dialogs/__tests__/TranslationApiDialog.test.js
@@ -42,7 +42,8 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
+ t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key),
+ locale: require('vue').ref('en')
})
}));
diff --git a/src/views/Settings/dialogs/__tests__/VRChatConfigDialog.test.js b/src/views/Settings/dialogs/__tests__/VRChatConfigDialog.test.js
index c9c3681d..6de65b5b 100644
--- a/src/views/Settings/dialogs/__tests__/VRChatConfigDialog.test.js
+++ b/src/views/Settings/dialogs/__tests__/VRChatConfigDialog.test.js
@@ -49,7 +49,8 @@ vi.mock('pinia', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key)
+ t: (key, params) => (params ? `${key}:${JSON.stringify(params)}` : key),
+ locale: require('vue').ref('en')
})
}));
diff --git a/src/views/Sidebar/components/__tests__/FriendItem.test.js b/src/views/Sidebar/components/__tests__/FriendItem.test.js
index 7ce18f0e..13b8779c 100644
--- a/src/views/Sidebar/components/__tests__/FriendItem.test.js
+++ b/src/views/Sidebar/components/__tests__/FriendItem.test.js
@@ -33,7 +33,9 @@ vi.mock('../../../../shared/utils', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('@/components/ui/avatar', () => ({
diff --git a/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js b/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js
index 69739067..a3197678 100644
--- a/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js
+++ b/src/views/Sidebar/components/__tests__/FriendsSidebar.test.js
@@ -150,7 +150,8 @@ vi.mock('vue-sonner', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
- t: (key) => key
+ t: (key) => key,
+ locale: require('vue').ref('en')
})
}));
diff --git a/src/views/Sidebar/components/__tests__/NotificationCenterSheet.test.js b/src/views/Sidebar/components/__tests__/NotificationCenterSheet.test.js
index 31f8385a..9a17ff9e 100644
--- a/src/views/Sidebar/components/__tests__/NotificationCenterSheet.test.js
+++ b/src/views/Sidebar/components/__tests__/NotificationCenterSheet.test.js
@@ -26,7 +26,9 @@ vi.mock('vue-router', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('../../../../stores', () => ({
diff --git a/src/views/Sidebar/components/__tests__/NotificationItem.test.js b/src/views/Sidebar/components/__tests__/NotificationItem.test.js
index 6137aab4..c260f193 100644
--- a/src/views/Sidebar/components/__tests__/NotificationItem.test.js
+++ b/src/views/Sidebar/components/__tests__/NotificationItem.test.js
@@ -52,7 +52,8 @@ vi.mock('../../../../shared/utils', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key,
- te: () => false
+ te: () => false,
+ locale: require('vue').ref('en')
})
}));
@@ -72,13 +73,19 @@ vi.mock('@/components/ui/avatar', () => ({
props: ['src'],
template: '
'
},
- AvatarFallback: { template: '' }
+ AvatarFallback: {
+ template: ''
+ }
}));
vi.mock('@/components/ui/hover-card', () => ({
HoverCard: { template: '
' },
- HoverCardTrigger: { template: '
' },
- HoverCardContent: { template: '
' }
+ HoverCardTrigger: {
+ template: '
'
+ },
+ HoverCardContent: {
+ template: '
'
+ }
}));
vi.mock('@/components/ui/badge', () => ({
@@ -197,9 +204,9 @@ describe('NotificationItem.vue', () => {
});
await wrapper.get('[data-icon="Link"]').trigger('click');
- expect(mocks.notificationStore.openNotificationLink).toHaveBeenCalledWith(
- 'group:grp_123'
- );
+ expect(
+ mocks.notificationStore.openNotificationLink
+ ).toHaveBeenCalledWith('group:grp_123');
});
test('unmount queues mark-as-seen for unseen notification', () => {
diff --git a/src/views/Sidebar/components/__tests__/NotificationList.test.js b/src/views/Sidebar/components/__tests__/NotificationList.test.js
index 99cf44fd..dcde57a8 100644
--- a/src/views/Sidebar/components/__tests__/NotificationList.test.js
+++ b/src/views/Sidebar/components/__tests__/NotificationList.test.js
@@ -22,7 +22,9 @@ vi.mock('@tanstack/vue-virtual', () => ({
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key
- })
+ ,
+ locale: require('vue').ref('en')
+ })
}));
vi.mock('@/components/ui/button', () => ({