mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
use item component for tools item
This commit is contained in:
@@ -226,7 +226,7 @@ describe('UserDialogInfoTab.vue', () => {
|
||||
wrapper.vm.onTabActivated();
|
||||
await flushPromises();
|
||||
|
||||
expect(creditsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(creditsSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -12,32 +12,16 @@
|
||||
<span class="category-title">{{ t('view.tools.pictures.header') }}</span>
|
||||
</div>
|
||||
<div class="tools-grid" v-show="!categoryCollapsed['image']">
|
||||
<Card class="tool-card x-hover-card p-0 gap-0 hover:bg-accent hover:shadow-sm">
|
||||
<div class="tool-content text-2xl" @click="showScreenshotMetadataPage">
|
||||
<div class="tool-icon">
|
||||
<Camera />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.pictures.screenshot') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.pictures.screenshot_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showGalleryPage">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Images />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.pictures.inventory') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.pictures.inventory_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<ToolItem
|
||||
:icon="Camera"
|
||||
:title="t('view.tools.pictures.screenshot')"
|
||||
:description="t('view.tools.pictures.screenshot_description')"
|
||||
@click="showScreenshotMetadataPage" />
|
||||
<ToolItem
|
||||
:icon="Images"
|
||||
:title="t('view.tools.pictures.inventory')"
|
||||
:description="t('view.tools.pictures.inventory_description')"
|
||||
@click="showGalleryPage" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,73 +33,31 @@
|
||||
<span class="category-title">{{ t('view.tools.shortcuts.header') }}</span>
|
||||
</div>
|
||||
<div class="tools-grid" v-show="!categoryCollapsed['shortcuts']">
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="openVrcPhotosFolder">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Folder />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.pictures.pictures.vrc_photos') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.pictures.pictures.vrc_photos_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="openVrcScreenshotsFolder">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Folder />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">
|
||||
{{ t('view.tools.pictures.pictures.steam_screenshots') }}
|
||||
</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.pictures.pictures.steam_screenshots_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="openVrcxAppDataFolder">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Folder />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.shortcuts.vrcx_data') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.shortcuts.vrcx_data_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="openVrcAppDataFolder">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Folder />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.shortcuts.vrchat_data') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.shortcuts.vrchat_data_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="openCrashVrcCrashDumps">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Folder />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.shortcuts.crash_dumps') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.shortcuts.crash_dumps_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<ToolItem
|
||||
:icon="Folder"
|
||||
:title="t('view.tools.pictures.pictures.vrc_photos')"
|
||||
:description="t('view.tools.pictures.pictures.vrc_photos_description')"
|
||||
@click="openVrcPhotosFolder" />
|
||||
<ToolItem
|
||||
:icon="Folder"
|
||||
:title="t('view.tools.pictures.pictures.steam_screenshots')"
|
||||
:description="t('view.tools.pictures.pictures.steam_screenshots_description')"
|
||||
@click="openVrcScreenshotsFolder" />
|
||||
<ToolItem
|
||||
:icon="Folder"
|
||||
:title="t('view.tools.shortcuts.vrcx_data')"
|
||||
:description="t('view.tools.shortcuts.vrcx_data_description')"
|
||||
@click="openVrcxAppDataFolder" />
|
||||
<ToolItem
|
||||
:icon="Folder"
|
||||
:title="t('view.tools.shortcuts.vrchat_data')"
|
||||
:description="t('view.tools.shortcuts.vrchat_data_description')"
|
||||
@click="openVrcAppDataFolder" />
|
||||
<ToolItem
|
||||
:icon="Folder"
|
||||
:title="t('view.tools.shortcuts.crash_dumps')"
|
||||
:description="t('view.tools.shortcuts.crash_dumps_description')"
|
||||
@click="openCrashVrcCrashDumps" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,64 +69,26 @@
|
||||
<span class="category-title">{{ t('view.tools.system_tools.header') }}</span>
|
||||
</div>
|
||||
<div class="tools-grid" v-show="!categoryCollapsed['system']">
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showVRChatConfig">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Settings />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.system_tools.vrchat_config') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.system_tools.vrchat_config_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showLaunchOptions">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Settings />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">
|
||||
{{ t('view.settings.advanced.advanced.launch_options') }}
|
||||
</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.system_tools.launch_options_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showRegistryBackupDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Settings />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">
|
||||
{{ t('view.settings.advanced.advanced.vrc_registry_backup') }}
|
||||
</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.system_tools.registry_backup_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showAutoChangeStatusDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<Settings />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">
|
||||
{{ t('view.settings.general.automation.auto_change_status') }}
|
||||
</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.settings.general.automation.auto_state_change_tooltip') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<ToolItem
|
||||
:icon="Settings"
|
||||
:title="t('view.tools.system_tools.vrchat_config')"
|
||||
:description="t('view.tools.system_tools.vrchat_config_description')"
|
||||
@click="showVRChatConfig" />
|
||||
<ToolItem
|
||||
:icon="Settings"
|
||||
:title="t('view.settings.advanced.advanced.launch_options')"
|
||||
:description="t('view.tools.system_tools.launch_options_description')"
|
||||
@click="showLaunchOptions" />
|
||||
<ToolItem
|
||||
:icon="Settings"
|
||||
:title="t('view.settings.advanced.advanced.vrc_registry_backup')"
|
||||
:description="t('view.tools.system_tools.registry_backup_description')"
|
||||
@click="showRegistryBackupDialog" />
|
||||
<ToolItem
|
||||
:icon="Settings"
|
||||
:title="t('view.settings.general.automation.auto_change_status')"
|
||||
:description="t('view.settings.general.automation.auto_state_change_tooltip')"
|
||||
@click="showAutoChangeStatusDialog" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -196,17 +100,11 @@
|
||||
<span class="category-title">{{ t('view.tools.group.header') }}</span>
|
||||
</div>
|
||||
<div class="tools-grid" v-show="!categoryCollapsed['group']">
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showGroupCalendarDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<CalendarDays />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.group.calendar') }}</div>
|
||||
<div class="tool-description">{{ t('view.tools.group.calendar_description') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<ToolItem
|
||||
:icon="CalendarDays"
|
||||
:title="t('view.tools.group.calendar')"
|
||||
:description="t('view.tools.group.calendar_description')"
|
||||
@click="showGroupCalendarDialog" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,59 +115,26 @@
|
||||
</div>
|
||||
|
||||
<div class="tools-grid" v-show="!categoryCollapsed['user']">
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showExportDiscordNamesDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<FolderInput />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.export.discord_names') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.user.discord_names_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showNoteExportDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<FolderInput />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.export.export_notes') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.export.export_notes_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showExportFriendsListDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<FolderInput />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.export.export_friend_list') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.user.export_friend_list_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showExportAvatarsListDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<FolderInput />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.export.export_own_avatars') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.user.export_own_avatars_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<ToolItem
|
||||
:icon="FolderInput"
|
||||
:title="t('view.tools.export.discord_names')"
|
||||
:description="t('view.tools.user.discord_names_description')"
|
||||
@click="showExportDiscordNamesDialog" />
|
||||
<ToolItem
|
||||
:icon="FolderInput"
|
||||
:title="t('view.tools.export.export_notes')"
|
||||
:description="t('view.tools.export.export_notes_description')"
|
||||
@click="showNoteExportDialog" />
|
||||
<ToolItem
|
||||
:icon="FolderInput"
|
||||
:title="t('view.tools.export.export_friend_list')"
|
||||
:description="t('view.tools.user.export_friend_list_description')"
|
||||
@click="showExportFriendsListDialog" />
|
||||
<ToolItem
|
||||
:icon="FolderInput"
|
||||
:title="t('view.tools.export.export_own_avatars')"
|
||||
:description="t('view.tools.user.export_own_avatars_description')"
|
||||
@click="showExportAvatarsListDialog" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -281,19 +146,11 @@
|
||||
<span class="category-title">{{ t('view.tools.other.header') }}</span>
|
||||
</div>
|
||||
<div class="tools-grid" v-show="!categoryCollapsed['other']">
|
||||
<Card class="tool-card p-0 gap-0">
|
||||
<div class="tool-content" @click="showEditInviteMessageDialog">
|
||||
<div class="tool-icon text-2xl">
|
||||
<SquarePen />
|
||||
</div>
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">{{ t('view.tools.other.edit_invite_message') }}</div>
|
||||
<div class="tool-description">
|
||||
{{ t('view.tools.other.edit_invite_message_description') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<ToolItem
|
||||
:icon="SquarePen"
|
||||
:title="t('view.tools.other.edit_invite_message')"
|
||||
:description="t('view.tools.other.edit_invite_message_description')"
|
||||
@click="showEditInviteMessageDialog" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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);
|
||||
|
||||
99
src/views/Tools/__tests__/Tools.test.js
Normal file
99
src/views/Tools/__tests__/Tools.test.js
Normal file
@@ -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: '<div />' }
|
||||
}));
|
||||
|
||||
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)
|
||||
);
|
||||
});
|
||||
});
|
||||
21
src/views/Tools/components/ToolItem.vue
Normal file
21
src/views/Tools/components/ToolItem.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from '@/components/ui/item';
|
||||
|
||||
defineProps({
|
||||
icon: { type: [Object, Function], required: true },
|
||||
title: { type: String, required: true },
|
||||
description: { type: String, required: true }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Item variant="outline" class="cursor-pointer hover:bg-accent/50">
|
||||
<ItemMedia variant="icon">
|
||||
<component :is="icon" />
|
||||
</ItemMedia>
|
||||
<ItemContent>
|
||||
<ItemTitle>{{ title }}</ItemTitle>
|
||||
<ItemDescription>{{ description }}</ItemDescription>
|
||||
</ItemContent>
|
||||
</Item>
|
||||
</template>
|
||||
25
src/views/Tools/components/__tests__/ToolItem.test.js
Normal file
25
src/views/Tools/components/__tests__/ToolItem.test.js
Normal file
@@ -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: '<svg data-test="mock-icon" />'
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user