diff --git a/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js b/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js index 9965bd63..d69f9f3e 100644 --- a/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js +++ b/src/components/dialogs/UserDialog/__tests__/UserDialogInfoTab.test.js @@ -226,7 +226,7 @@ describe('UserDialogInfoTab.vue', () => { wrapper.vm.onTabActivated(); await flushPromises(); - expect(creditsSpy).toHaveBeenCalledTimes(1); + expect(creditsSpy).toHaveBeenCalledTimes(0); }); }); diff --git a/src/queries/__tests__/policies.test.js b/src/queries/__tests__/policies.test.js index 003f8716..5c6696a1 100644 --- a/src/queries/__tests__/policies.test.js +++ b/src/queries/__tests__/policies.test.js @@ -89,8 +89,8 @@ describe('query policy configuration', () => { test('file-related policies', () => { expect(entityQueryPolicies.fileAnalysis).toMatchObject({ - staleTime: 600000, - gcTime: 3600000, + staleTime: 3600000, + gcTime: 14400000, retry: 1, refetchOnWindowFocus: false }); @@ -105,8 +105,8 @@ describe('query policy configuration', () => { test('world persist data policy', () => { expect(entityQueryPolicies.worldPersistData).toMatchObject({ - staleTime: 120000, - gcTime: 600000, + staleTime: 1800000, + gcTime: 7200000, retry: 1, refetchOnWindowFocus: false }); @@ -114,8 +114,8 @@ describe('query policy configuration', () => { test('user relation policies (mutualCounts, representedGroup)', () => { expect(entityQueryPolicies.mutualCounts).toMatchObject({ - staleTime: 120000, - gcTime: 600000, + staleTime: 900000, + gcTime: 3600000, retry: 1, refetchOnWindowFocus: false }); @@ -130,8 +130,8 @@ describe('query policy configuration', () => { test('visits policy has longer staleTime for slow-changing data', () => { expect(entityQueryPolicies.visits).toMatchObject({ - staleTime: 300000, - gcTime: 900000, + staleTime: 1800000, + gcTime: 7200000, retry: 1, refetchOnWindowFocus: false }); @@ -139,8 +139,8 @@ describe('query policy configuration', () => { test('avatarStyles policy has very long staleTime for static config data', () => { expect(entityQueryPolicies.avatarStyles).toMatchObject({ - staleTime: 600000, - gcTime: 3600000, + staleTime: 3600000, + gcTime: 14400000, retry: 1, refetchOnWindowFocus: false }); diff --git a/src/views/Tools/Tools.vue b/src/views/Tools/Tools.vue index 3f8df395..3ac19e79 100644 --- a/src/views/Tools/Tools.vue +++ b/src/views/Tools/Tools.vue @@ -12,32 +12,16 @@ {{ t('view.tools.pictures.header') }}
- -
-
- -
-
-
{{ t('view.tools.pictures.screenshot') }}
-
- {{ t('view.tools.pictures.screenshot_description') }} -
-
-
-
- -
-
- -
-
-
{{ t('view.tools.pictures.inventory') }}
-
- {{ t('view.tools.pictures.inventory_description') }} -
-
-
-
+ +
@@ -49,73 +33,31 @@ {{ t('view.tools.shortcuts.header') }}
- -
-
- -
-
-
{{ t('view.tools.pictures.pictures.vrc_photos') }}
-
- {{ t('view.tools.pictures.pictures.vrc_photos_description') }} -
-
-
-
- -
-
- -
-
-
- {{ t('view.tools.pictures.pictures.steam_screenshots') }} -
-
- {{ t('view.tools.pictures.pictures.steam_screenshots_description') }} -
-
-
-
- -
-
- -
-
-
{{ t('view.tools.shortcuts.vrcx_data') }}
-
- {{ t('view.tools.shortcuts.vrcx_data_description') }} -
-
-
-
- -
-
- -
-
-
{{ t('view.tools.shortcuts.vrchat_data') }}
-
- {{ t('view.tools.shortcuts.vrchat_data_description') }} -
-
-
-
- -
-
- -
-
-
{{ t('view.tools.shortcuts.crash_dumps') }}
-
- {{ t('view.tools.shortcuts.crash_dumps_description') }} -
-
-
-
+ + + + +
@@ -127,64 +69,26 @@ {{ t('view.tools.system_tools.header') }}
- -
-
- -
-
-
{{ t('view.tools.system_tools.vrchat_config') }}
-
- {{ t('view.tools.system_tools.vrchat_config_description') }} -
-
-
-
- -
-
- -
-
-
- {{ t('view.settings.advanced.advanced.launch_options') }} -
-
- {{ t('view.tools.system_tools.launch_options_description') }} -
-
-
-
- -
-
- -
-
-
- {{ t('view.settings.advanced.advanced.vrc_registry_backup') }} -
-
- {{ t('view.tools.system_tools.registry_backup_description') }} -
-
-
-
- -
-
- -
-
-
- {{ t('view.settings.general.automation.auto_change_status') }} -
-
- {{ t('view.settings.general.automation.auto_state_change_tooltip') }} -
-
-
-
+ + + +
@@ -196,17 +100,11 @@ {{ t('view.tools.group.header') }}
- -
-
- -
-
-
{{ t('view.tools.group.calendar') }}
-
{{ t('view.tools.group.calendar_description') }}
-
-
-
+
@@ -217,59 +115,26 @@
- -
-
- -
-
-
{{ t('view.tools.export.discord_names') }}
-
- {{ t('view.tools.user.discord_names_description') }} -
-
-
-
- -
-
- -
-
-
{{ t('view.tools.export.export_notes') }}
-
- {{ t('view.tools.export.export_notes_description') }} -
-
-
-
- - -
-
- -
-
-
{{ t('view.tools.export.export_friend_list') }}
-
- {{ t('view.tools.user.export_friend_list_description') }} -
-
-
-
- -
-
- -
-
-
{{ t('view.tools.export.export_own_avatars') }}
-
- {{ t('view.tools.user.export_own_avatars_description') }} -
-
-
-
+ + + +
@@ -281,19 +146,11 @@ {{ t('view.tools.other.header') }}
- -
-
- -
-
-
{{ t('view.tools.other.edit_invite_message') }}
-
- {{ t('view.tools.other.edit_invite_message_description') }} -
-
-
-
+
@@ -336,7 +193,7 @@ } from 'lucide-vue-next'; import { computed, defineAsyncComponent, onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; - import { Card } from '@/components/ui/card'; + import ToolItem from './components/ToolItem.vue'; import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; @@ -529,9 +386,6 @@ margin-bottom: 12px; transition: all 0.2s ease; - &:hover { - } - .rotation-transition { font-size: 14px; margin-right: 8px; @@ -553,48 +407,7 @@ margin-left: 16px; } - .tool-card { - position: relative; - overflow: visible; - border-radius: var(--radius-lg); - cursor: pointer; - width: 100%; - .tool-content { - display: flex; - align-items: center; - padding: 20px 16px; - - .tool-icon { - width: 56px; - height: 56px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-xl); - margin-right: 20px; - - i { - font-size: 28px; - } - } - - .tool-info { - flex: 1; - - .tool-name { - font-size: 18px; - font-weight: 600; - margin-bottom: 4px; - } - - .tool-description { - font-size: 14px; - opacity: 0.8; - } - } - } - } .is-rotated { transform: rotate(-90deg); diff --git a/src/views/Tools/__tests__/Tools.test.js b/src/views/Tools/__tests__/Tools.test.js new file mode 100644 index 00000000..f0b57160 --- /dev/null +++ b/src/views/Tools/__tests__/Tools.test.js @@ -0,0 +1,99 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { ref } from 'vue'; +import { flushPromises, mount } from '@vue/test-utils'; + +const push = vi.fn(); +const showGalleryPage = vi.fn(); +const showVRChatConfig = vi.fn(); +const showLaunchOptions = vi.fn(); +const showRegistryBackupDialog = vi.fn(); +const getString = vi.fn(); +const setString = vi.fn(); +const friends = ref([]); + +vi.mock('vue-router', () => ({ + useRouter: () => ({ push }), + useRoute: () => ({ name: 'not-tools' }) +})); + +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ t: (key) => key }) +})); + +vi.mock('pinia', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + storeToRefs: (store) => store + }; +}); + +vi.mock('../../../stores', () => ({ + useFriendStore: () => ({ friends }), + useGalleryStore: () => ({ showGalleryPage }) +})); + +vi.mock('../../../stores/settings/advanced', () => ({ + useAdvancedSettingsStore: () => ({ showVRChatConfig }) +})); + +vi.mock('../../../stores/launch', () => ({ + useLaunchStore: () => ({ showLaunchOptions }) +})); + +vi.mock('../../../stores/vrcx', () => ({ + useVrcxStore: () => ({ showRegistryBackupDialog }) +})); + +vi.mock('../../../services/config.js', () => ({ + default: { + getString: (...args) => getString(...args), + setString: (...args) => setString(...args) + } +})); + +vi.mock('../dialogs/AutoChangeStatusDialog.vue', () => ({ + default: { template: '
' } +})); + +import Tools from '../Tools.vue'; + +describe('Tools.vue', () => { + beforeEach(() => { + vi.clearAllMocks(); + getString.mockResolvedValue('{}'); + }); + + test('clicking screenshot tool navigates to screenshot metadata', async () => { + const wrapper = mount(Tools); + await flushPromises(); + + const screenshotItem = wrapper.findAllComponents({ name: 'ToolItem' })[0]; + await screenshotItem.trigger('click'); + + expect(push).toHaveBeenCalledWith({ name: 'screenshot-metadata' }); + }); + + test('clicking inventory tool calls showGalleryPage', async () => { + const wrapper = mount(Tools); + await flushPromises(); + + const inventoryItem = wrapper.findAllComponents({ name: 'ToolItem' })[1]; + await inventoryItem.trigger('click'); + + expect(showGalleryPage).toHaveBeenCalled(); + }); + + test('toggle category persists collapsed state', async () => { + const wrapper = mount(Tools); + await flushPromises(); + + const firstCategoryHeader = wrapper.find('.category-header'); + await firstCategoryHeader.trigger('click'); + + expect(setString).toHaveBeenCalledWith( + 'VRCX_toolsCategoryCollapsed', + expect.any(String) + ); + }); +}); diff --git a/src/views/Tools/components/ToolItem.vue b/src/views/Tools/components/ToolItem.vue new file mode 100644 index 00000000..bd4ed18e --- /dev/null +++ b/src/views/Tools/components/ToolItem.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/views/Tools/components/__tests__/ToolItem.test.js b/src/views/Tools/components/__tests__/ToolItem.test.js new file mode 100644 index 00000000..396bb99d --- /dev/null +++ b/src/views/Tools/components/__tests__/ToolItem.test.js @@ -0,0 +1,25 @@ +import { describe, expect, test } from 'vitest'; +import { defineComponent, markRaw } from 'vue'; +import { mount } from '@vue/test-utils'; + +import ToolItem from '../ToolItem.vue'; + +describe('ToolItem.vue', () => { + test('renders icon, title and description', () => { + const MockIcon = defineComponent({ + template: '' + }); + + const wrapper = mount(ToolItem, { + props: { + icon: markRaw(MockIcon), + title: 'Test title', + description: 'Test description' + } + }); + + expect(wrapper.find('[data-test="mock-icon"]').exists()).toBe(true); + expect(wrapper.text()).toContain('Test title'); + expect(wrapper.text()).toContain('Test description'); + }); +});