mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-30 04:03:48 +02:00
add test
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
showFavoriteDialog: vi.fn(),
|
||||
showAvatarDialog: vi.fn(),
|
||||
selectAvatarWithConfirmation: vi.fn(),
|
||||
deleteFavorite: vi.fn(),
|
||||
removeLocalAvatarFavorite: vi.fn(),
|
||||
currentUser: { currentAvatar: '' }
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
storeToRefs: (store) => store
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useFavoriteStore: () => ({
|
||||
showFavoriteDialog: (...args) => mocks.showFavoriteDialog(...args)
|
||||
}),
|
||||
useUserStore: () => ({
|
||||
currentUser: mocks.currentUser
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/avatarCoordinator', () => ({
|
||||
showAvatarDialog: (...args) => mocks.showAvatarDialog(...args),
|
||||
selectAvatarWithConfirmation: (...args) => mocks.selectAvatarWithConfirmation(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/favoriteCoordinator', () => ({
|
||||
removeLocalAvatarFavorite: (...args) => mocks.removeLocalAvatarFavorite(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../api', () => ({
|
||||
favoriteRequest: {
|
||||
deleteFavorite: (...args) => mocks.deleteFavorite(...args)
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/item', () => ({
|
||||
Item: {
|
||||
emits: ['click'],
|
||||
template: '<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
},
|
||||
ItemActions: { template: '<div><slot /></div>' },
|
||||
ItemMedia: { template: '<div><slot /></div>' },
|
||||
ItemContent: { template: '<div><slot /></div>' },
|
||||
ItemTitle: { template: '<div><slot /></div>' },
|
||||
ItemDescription: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/avatar', () => ({
|
||||
Avatar: { template: '<div data-testid="avatar"><slot /></div>' },
|
||||
AvatarImage: {
|
||||
props: ['src'],
|
||||
template: '<img data-testid="avatar-image" :src="src" :class="$attrs.class" />'
|
||||
},
|
||||
AvatarFallback: { template: '<span data-testid="avatar-fallback" :class="$attrs.class"><slot /></span>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="button" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/checkbox', () => ({
|
||||
Checkbox: {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
template:
|
||||
'<input data-testid="checkbox" type="checkbox" :checked="modelValue" @change="$emit(\'update:modelValue\', $event.target.checked)" />'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/context-menu', () => ({
|
||||
ContextMenu: { template: '<div><slot /></div>' },
|
||||
ContextMenuTrigger: { template: '<div><slot /></div>' },
|
||||
ContextMenuContent: { template: '<div><slot /></div>' },
|
||||
ContextMenuSeparator: { template: '<hr />' },
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
DropdownMenu: { template: '<div><slot /></div>' },
|
||||
DropdownMenuTrigger: { template: '<div><slot /></div>' },
|
||||
DropdownMenuContent: { template: '<div><slot /></div>' },
|
||||
DropdownMenuSeparator: { template: '<hr />' },
|
||||
DropdownMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
AlertTriangle: { template: '<i />' },
|
||||
Lock: { template: '<i />' },
|
||||
MoreHorizontal: { template: '<i />' },
|
||||
Trash2: { template: '<i />' }
|
||||
}));
|
||||
|
||||
import FavoritesAvatarItem from '../FavoritesAvatarItem.vue';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Record<string, any>} props
|
||||
*/
|
||||
function mountItem(props = {}) {
|
||||
return mount(FavoritesAvatarItem, {
|
||||
props: {
|
||||
favorite: {
|
||||
id: 'avtr_1',
|
||||
ref: {
|
||||
name: 'Avatar One',
|
||||
authorName: 'Author',
|
||||
thumbnailImageUrl: 'https://example.com/avatar_256.png',
|
||||
releaseStatus: 'public'
|
||||
}
|
||||
},
|
||||
group: 'FavGroup',
|
||||
isLocalFavorite: false,
|
||||
editMode: false,
|
||||
selected: false,
|
||||
...props
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param wrapper
|
||||
* @param text
|
||||
*/
|
||||
async function clickMenuItem(wrapper, text) {
|
||||
const buttons = wrapper.findAll('button');
|
||||
const target = buttons.find((btn) => btn.text().includes(text));
|
||||
expect(target, `menu item not found: ${text}`).toBeTruthy();
|
||||
await target.trigger('click');
|
||||
}
|
||||
|
||||
describe('FavoritesAvatarItem.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.showFavoriteDialog.mockReset();
|
||||
mocks.showAvatarDialog.mockReset();
|
||||
mocks.selectAvatarWithConfirmation.mockReset();
|
||||
mocks.deleteFavorite.mockReset();
|
||||
mocks.removeLocalAvatarFavorite.mockReset();
|
||||
mocks.currentUser.currentAvatar = '';
|
||||
});
|
||||
|
||||
it('opens avatar details when item is clicked', async () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
await wrapper.get('[data-testid="item"]').trigger('click');
|
||||
|
||||
expect(mocks.showAvatarDialog).toHaveBeenCalledWith('avtr_1');
|
||||
});
|
||||
|
||||
it('adds the unified hover classes on item', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
);
|
||||
});
|
||||
|
||||
it('uses rounded avatar shell and 128 thumbnail image', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="avatar"]').classes()).toEqual(expect.arrayContaining(['rounded-sm', 'size-full']));
|
||||
expect(wrapper.get('[data-testid="avatar-image"]').attributes('src')).toContain('avatar_128.png');
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').classes()).toContain('rounded-sm');
|
||||
});
|
||||
|
||||
it('shows fallback text when thumbnail is missing', () => {
|
||||
const wrapper = mountItem({
|
||||
favorite: {
|
||||
id: 'avtr_no_thumb',
|
||||
ref: {
|
||||
name: 'Bravo',
|
||||
authorName: 'Author',
|
||||
thumbnailImageUrl: '',
|
||||
releaseStatus: 'public'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="avatar-image"]').exists()).toBe(false);
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').text()).toContain('B');
|
||||
});
|
||||
|
||||
it('uses local delete flow for local favorites', async () => {
|
||||
const wrapper = mountItem({
|
||||
isLocalFavorite: true,
|
||||
group: { name: 'LocalGroup' },
|
||||
favorite: {
|
||||
id: 'avtr_local',
|
||||
name: 'Local Avatar',
|
||||
thumbnailImageUrl: ''
|
||||
}
|
||||
});
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.delete_tooltip');
|
||||
|
||||
expect(mocks.removeLocalAvatarFavorite).toHaveBeenCalledWith('avtr_local', 'LocalGroup');
|
||||
expect(mocks.deleteFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses remote delete flow for remote favorites', async () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.unfavorite_tooltip');
|
||||
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({ objectId: 'avtr_1' });
|
||||
expect(mocks.removeLocalAvatarFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
showFavoriteDialog: vi.fn(),
|
||||
showAvatarDialog: vi.fn(),
|
||||
selectAvatarWithConfirmation: vi.fn(),
|
||||
currentUser: { currentAvatar: '' }
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
storeToRefs: (store) => store
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useFavoriteStore: () => ({
|
||||
showFavoriteDialog: (...args) => mocks.showFavoriteDialog(...args)
|
||||
}),
|
||||
useUserStore: () => ({
|
||||
currentUser: mocks.currentUser
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/avatarCoordinator', () => ({
|
||||
showAvatarDialog: (...args) => mocks.showAvatarDialog(...args),
|
||||
selectAvatarWithConfirmation: (...args) => mocks.selectAvatarWithConfirmation(...args)
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/item', () => ({
|
||||
Item: {
|
||||
emits: ['click'],
|
||||
template: '<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
},
|
||||
ItemActions: { template: '<div><slot /></div>' },
|
||||
ItemMedia: { template: '<div><slot /></div>' },
|
||||
ItemContent: { template: '<div><slot /></div>' },
|
||||
ItemTitle: { template: '<div><slot /></div>' },
|
||||
ItemDescription: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/avatar', () => ({
|
||||
Avatar: { template: '<div data-testid="avatar"><slot /></div>' },
|
||||
AvatarImage: {
|
||||
props: ['src'],
|
||||
template: '<img data-testid="avatar-image" :src="src" :class="$attrs.class" />'
|
||||
},
|
||||
AvatarFallback: { template: '<span data-testid="avatar-fallback" :class="$attrs.class"><slot /></span>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="button" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/context-menu', () => ({
|
||||
ContextMenu: { template: '<div><slot /></div>' },
|
||||
ContextMenuTrigger: { template: '<div><slot /></div>' },
|
||||
ContextMenuContent: { template: '<div><slot /></div>' },
|
||||
ContextMenuSeparator: { template: '<hr />' },
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
DropdownMenu: { template: '<div><slot /></div>' },
|
||||
DropdownMenuTrigger: { template: '<div><slot /></div>' },
|
||||
DropdownMenuContent: { template: '<div><slot /></div>' },
|
||||
DropdownMenuSeparator: { template: '<hr />' },
|
||||
DropdownMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
MoreHorizontal: { template: '<i />' }
|
||||
}));
|
||||
|
||||
import FavoritesAvatarLocalHistoryItem from '../FavoritesAvatarLocalHistoryItem.vue';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Record<string, any>} props
|
||||
*/
|
||||
function mountItem(props = {}) {
|
||||
return mount(FavoritesAvatarLocalHistoryItem, {
|
||||
props: {
|
||||
favorite: {
|
||||
id: 'avtr_hist_1',
|
||||
name: 'Local History Avatar',
|
||||
authorName: 'Author',
|
||||
thumbnailImageUrl: 'https://example.com/history_256.png',
|
||||
...props.favorite
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param wrapper
|
||||
* @param text
|
||||
*/
|
||||
async function clickMenuItem(wrapper, text) {
|
||||
const buttons = wrapper.findAll('button');
|
||||
const target = buttons.find((btn) => btn.text().includes(text));
|
||||
expect(target, `menu item not found: ${text}`).toBeTruthy();
|
||||
await target.trigger('click');
|
||||
}
|
||||
|
||||
describe('FavoritesAvatarLocalHistoryItem.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.showFavoriteDialog.mockReset();
|
||||
mocks.showAvatarDialog.mockReset();
|
||||
mocks.selectAvatarWithConfirmation.mockReset();
|
||||
mocks.currentUser.currentAvatar = '';
|
||||
});
|
||||
|
||||
it('opens avatar details when item is clicked', async () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
await wrapper.get('[data-testid="item"]').trigger('click');
|
||||
|
||||
expect(mocks.showAvatarDialog).toHaveBeenCalledWith('avtr_hist_1');
|
||||
});
|
||||
|
||||
it('adds the unified hover classes on item', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
);
|
||||
});
|
||||
|
||||
it('uses rounded avatar shell and 128 thumbnail image', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="avatar"]').classes()).toEqual(expect.arrayContaining(['rounded-sm', 'size-full']));
|
||||
expect(wrapper.get('[data-testid="avatar-image"]').attributes('src')).toContain('history_128.png');
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').classes()).toContain('rounded-sm');
|
||||
});
|
||||
|
||||
it('shows fallback text when thumbnail is missing', () => {
|
||||
const wrapper = mountItem({
|
||||
favorite: {
|
||||
id: 'avtr_hist_no_thumb',
|
||||
name: 'Charlie',
|
||||
thumbnailImageUrl: ''
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="avatar-image"]').exists()).toBe(false);
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').text()).toContain('C');
|
||||
});
|
||||
|
||||
it('runs select-avatar action from menu', async () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.select_avatar_tooltip');
|
||||
|
||||
expect(mocks.selectAvatarWithConfirmation).toHaveBeenCalledWith('avtr_hist_1');
|
||||
});
|
||||
});
|
||||
@@ -240,6 +240,14 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
expect(mocks.showUserDialog).toHaveBeenCalledWith('usr_1');
|
||||
});
|
||||
|
||||
it('adds the unified hover classes on item', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
);
|
||||
});
|
||||
|
||||
it('emits toggle-select in edit mode checkbox', async () => {
|
||||
const wrapper = mountItem({ editMode: true });
|
||||
|
||||
|
||||
@@ -76,6 +76,15 @@ vi.mock('@/components/ui/item', () => ({
|
||||
ItemDescription: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/avatar', () => ({
|
||||
Avatar: { template: '<div data-testid="avatar"><slot /></div>' },
|
||||
AvatarImage: {
|
||||
props: ['src'],
|
||||
template: '<img data-testid="avatar-image" :src="src" />'
|
||||
},
|
||||
AvatarFallback: { template: '<span data-testid="avatar-fallback"><slot /></span>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
@@ -245,6 +254,32 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
expect(wrapper.text()).toContain('wrld_missing_ref');
|
||||
});
|
||||
|
||||
it('adds the unified hover classes on item', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
);
|
||||
});
|
||||
|
||||
it('uses rounded avatar fallback when thumbnail is missing', () => {
|
||||
const wrapper = mountItem({
|
||||
favorite: {
|
||||
id: 'wrld_no_thumb',
|
||||
ref: {
|
||||
name: 'No Thumb World',
|
||||
authorName: 'Author',
|
||||
thumbnailImageUrl: '',
|
||||
releaseStatus: 'public'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="avatar-image"]').exists()).toBe(false);
|
||||
expect(wrapper.get('[data-testid="avatar"]').classes()).toEqual(expect.arrayContaining(['rounded-sm', 'size-full']));
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').classes()).toContain('rounded-sm');
|
||||
});
|
||||
|
||||
it('deletes local favorite via coordinator', async () => {
|
||||
const wrapper = mountItem({
|
||||
favorite: {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { defineComponent, h } from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getString: vi.fn(async (_key, fallback) => fallback),
|
||||
setString: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('../../../../services/config.js', () => ({
|
||||
default: {
|
||||
getString: (...args) => mocks.getString(...args),
|
||||
setString: (...args) => mocks.setString(...args)
|
||||
}
|
||||
}));
|
||||
|
||||
import { useFavoritesCardScaling } from '../useFavoritesCardScaling';
|
||||
|
||||
function mountComposable() {
|
||||
let api;
|
||||
const Comp = defineComponent({
|
||||
setup() {
|
||||
api = useFavoritesCardScaling({
|
||||
configKey: 'scale-key',
|
||||
spacingConfigKey: 'spacing-key'
|
||||
});
|
||||
return () => h('div');
|
||||
}
|
||||
});
|
||||
mount(Comp);
|
||||
return api;
|
||||
}
|
||||
|
||||
describe('useFavoritesCardScaling', () => {
|
||||
beforeEach(() => {
|
||||
mocks.getString.mockClear();
|
||||
mocks.setString.mockClear();
|
||||
});
|
||||
|
||||
it('builds grid style css vars from scale/spacing', async () => {
|
||||
const api = mountComposable();
|
||||
api.cardScale.value = 0.8;
|
||||
api.cardSpacing.value = 1.2;
|
||||
|
||||
const style = api.gridStyle.value(3, { preferredColumns: 2 });
|
||||
|
||||
expect(style['--favorites-card-scale']).toBe('0.80');
|
||||
expect(style['--favorites-card-spacing-scale']).toBe('1.20');
|
||||
expect(Number(style['--favorites-grid-columns'])).toBeGreaterThanOrEqual(1);
|
||||
expect(mocks.setString).toHaveBeenCalledWith('scale-key', '0.8');
|
||||
expect(mocks.setString).toHaveBeenCalledWith('spacing-key', '1.2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { useFavoritesLocalGroups } from '../useFavoritesLocalGroups';
|
||||
|
||||
describe('useFavoritesLocalGroups', () => {
|
||||
it('starts, confirms and selects created local group', async () => {
|
||||
const createGroup = vi.fn();
|
||||
const selectGroup = vi.fn();
|
||||
const api = useFavoritesLocalGroups({ createGroup, selectGroup });
|
||||
|
||||
api.startLocalGroupCreation();
|
||||
expect(api.isCreatingLocalGroup.value).toBe(true);
|
||||
|
||||
api.newLocalGroupName.value = ' Local A ';
|
||||
api.handleLocalGroupCreationConfirm();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(createGroup).toHaveBeenCalledWith('Local A');
|
||||
expect(selectGroup).toHaveBeenCalledWith('local', 'Local A', { userInitiated: true });
|
||||
expect(api.isCreatingLocalGroup.value).toBe(false);
|
||||
});
|
||||
|
||||
it('cancels when name is empty', () => {
|
||||
const createGroup = vi.fn();
|
||||
const selectGroup = vi.fn();
|
||||
const api = useFavoritesLocalGroups({ createGroup, selectGroup });
|
||||
|
||||
api.startLocalGroupCreation();
|
||||
api.newLocalGroupName.value = ' ';
|
||||
api.handleLocalGroupCreationConfirm();
|
||||
|
||||
expect(createGroup).not.toHaveBeenCalled();
|
||||
expect(selectGroup).not.toHaveBeenCalled();
|
||||
expect(api.isCreatingLocalGroup.value).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { defineComponent, h } from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { beforeEach } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getString: vi.fn(async (_key, fallback) => fallback),
|
||||
setString: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('../../../../services/config.js', () => ({
|
||||
default: {
|
||||
getString: (...args) => mocks.getString(...args),
|
||||
setString: (...args) => mocks.setString(...args)
|
||||
}
|
||||
}));
|
||||
|
||||
import { useFavoritesSplitter } from '../useFavoritesSplitter';
|
||||
|
||||
function mountComposable() {
|
||||
let api;
|
||||
const Comp = defineComponent({
|
||||
setup() {
|
||||
api = useFavoritesSplitter({
|
||||
configKey: 'split-key',
|
||||
defaultSize: 240,
|
||||
minPx: 120,
|
||||
maxPx: 360
|
||||
});
|
||||
return () => h('div');
|
||||
}
|
||||
});
|
||||
mount(Comp);
|
||||
return api;
|
||||
}
|
||||
|
||||
describe('useFavoritesSplitter', () => {
|
||||
beforeEach(() => {
|
||||
mocks.setString.mockClear();
|
||||
});
|
||||
|
||||
it('persists layout size while dragging', () => {
|
||||
const api = mountComposable();
|
||||
api.splitterGroupRef.value = { getBoundingClientRect: () => ({ width: 1200 }) };
|
||||
|
||||
api.setDragging(true);
|
||||
api.handleLayout([25, 75]);
|
||||
api.setDragging(false);
|
||||
|
||||
expect(mocks.setString).toHaveBeenCalledWith('split-key', '300');
|
||||
});
|
||||
|
||||
it('ignores layout updates when not dragging', () => {
|
||||
const api = mountComposable();
|
||||
api.splitterGroupRef.value = { getBoundingClientRect: () => ({ width: 1200 }) };
|
||||
|
||||
api.handleLayout([20, 80]);
|
||||
|
||||
expect(mocks.setString).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
36
src/views/GameLog/__tests__/GameLog.test.js
Normal file
36
src/views/GameLog/__tests__/GameLog.test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const mocks = vi.hoisted(() => ({ lookup: vi.fn(), table: { value: { vip: false, filter: [], search: '' } } }));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
vi.mock('../../../stores', () => ({
|
||||
useGameLogStore: () => ({ gameLogTableLookup: (...a) => mocks.lookup(...a), gameLogTable: mocks.table, gameLogTableData: ref([]) }),
|
||||
useAppearanceSettingsStore: () => ({ tablePageSizes: [20, 50], tablePageSize: 20 }),
|
||||
useVrcxStore: () => ({ maxTableSize: 500 }),
|
||||
useModalStore: () => ({ confirm: vi.fn() })
|
||||
}));
|
||||
vi.mock('../../../components/ui/data-table', () => ({ DataTableLayout: { template: '<div><slot name="toolbar" /></div>' } }));
|
||||
vi.mock('../../../components/ui/input-group', () => ({ InputGroupField: { template: '<input />' } }));
|
||||
vi.mock('@/components/ui/select', () => ({ Select: { emits: ['update:modelValue'], template: '<button data-testid="sel" @click="$emit(\'update:modelValue\', [\'Event\'])"><slot /></button>' }, SelectTrigger: { template: '<div><slot /></div>' }, SelectValue: { template: '<div><slot /></div>' }, SelectContent: { template: '<div><slot /></div>' }, SelectGroup: { template: '<div><slot /></div>' }, SelectItem: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('@/components/ui/toggle', () => ({ Toggle: { template: '<button><slot /></button>' } }));
|
||||
vi.mock('lucide-vue-next', () => ({ Star: { template: '<i />' } }));
|
||||
vi.mock('../../../services/database', () => ({ database: { deleteGameLogEntry: vi.fn() } }));
|
||||
vi.mock('../../../shared/utils', () => ({ removeFromArray: vi.fn() }));
|
||||
vi.mock('../../../composables/useDataTableScrollHeight', () => ({ useDataTableScrollHeight: () => ({ tableStyle: ref({}) }) }));
|
||||
vi.mock('../../../lib/table/useVrcxVueTable', () => ({ useVrcxVueTable: () => ({ table: { getFilteredRowModel: () => ({ rows: [] }) }, pagination: ref({ pageIndex: 0, pageSize: 20 }) }) }));
|
||||
vi.mock('../columns.jsx', () => ({ createColumns: () => [] }));
|
||||
|
||||
import GameLog from '../GameLog.vue';
|
||||
|
||||
describe('GameLog.vue', () => {
|
||||
it('updates filter and triggers lookup when filter changes', async () => {
|
||||
const wrapper = mount(GameLog);
|
||||
await wrapper.get('[data-testid="sel"]').trigger('click');
|
||||
|
||||
expect(mocks.lookup).toHaveBeenCalled();
|
||||
expect(mocks.table.value.filter).toEqual(['Event']);
|
||||
});
|
||||
});
|
||||
39
src/views/Layout/__tests__/MainLayout.test.js
Normal file
39
src/views/Layout/__tests__/MainLayout.test.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const mocks = vi.hoisted(() => ({ replace: vi.fn(), setNavCollapsed: vi.fn(), setNavWidth: vi.fn() }));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ replace: (...a) => mocks.replace(...a) }) }));
|
||||
vi.mock('../../../services/watchState', () => ({ watchState: { isLoggedIn: false } }));
|
||||
vi.mock('../../../stores', () => ({ useAppearanceSettingsStore: () => ({ navWidth: ref(240), isNavCollapsed: ref(false), showStatusBar: ref(false), setNavCollapsed: (...a) => mocks.setNavCollapsed(...a), setNavWidth: (...a) => mocks.setNavWidth(...a) }) }));
|
||||
vi.mock('../../../composables/useMainLayoutResizable', () => ({ useMainLayoutResizable: () => ({ asideDefaultSize: 30, asideMinSize: 0, asideMaxPx: 480, mainDefaultSize: 70, handleLayout: vi.fn(), isAsideCollapsed: () => false, isAsideCollapsedStatic: false, isSideBarTabShow: ref(true) }) }));
|
||||
vi.mock('../../../components/ui/resizable', () => ({ ResizablePanelGroup: { template: '<div><slot :layout="[]" /></div>' }, ResizablePanel: { template: '<div><slot /></div>' }, ResizableHandle: { template: '<div />' } }));
|
||||
vi.mock('../../../components/ui/sidebar', () => ({ SidebarProvider: { template: '<div><slot /></div>' }, SidebarInset: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('../../../components/NavMenu.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Sidebar/Sidebar.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/StatusBar.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/dialogs/MainDialogContainer.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/FullscreenImagePreview.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/dialogs/ChooseFavoriteGroupDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/dialogs/LaunchDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Settings/dialogs/LaunchOptionsDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Favorites/dialogs/FriendImportDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Favorites/dialogs/WorldImportDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Favorites/dialogs/AvatarImportDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/dialogs/GroupDialog/GroupMemberModerationDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/dialogs/InviteGroupDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Settings/dialogs/VRChatConfigDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Settings/dialogs/PrimaryPasswordDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/dialogs/SendBoopDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../Settings/dialogs/ChangelogDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
|
||||
import MainLayout from '../MainLayout.vue';
|
||||
|
||||
describe('MainLayout.vue', () => {
|
||||
it('redirects to login when not logged in', () => {
|
||||
mount(MainLayout, { global: { stubs: { RouterView: { template: '<div />' }, KeepAlive: { template: '<div><slot /></div>' } } } });
|
||||
expect(mocks.replace).toHaveBeenCalledWith({ name: 'login' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
discordStore: {
|
||||
setDiscordActive: vi.fn(),
|
||||
setDiscordInstance: vi.fn(),
|
||||
setDiscordHideInvite: vi.fn(),
|
||||
setDiscordJoinButton: vi.fn(),
|
||||
setDiscordHideImage: vi.fn(),
|
||||
setDiscordShowPlatform: vi.fn(),
|
||||
setDiscordWorldIntegration: vi.fn(),
|
||||
setDiscordWorldNameAsDiscordStatus: vi.fn(),
|
||||
saveDiscordOption: vi.fn(),
|
||||
discordActive: { __v_isRef: true, value: true },
|
||||
discordInstance: { __v_isRef: true, value: true },
|
||||
discordHideInvite: { __v_isRef: true, value: false },
|
||||
discordJoinButton: { __v_isRef: true, value: true },
|
||||
discordHideImage: { __v_isRef: true, value: false },
|
||||
discordShowPlatform: { __v_isRef: true, value: true },
|
||||
discordWorldIntegration: { __v_isRef: true, value: true },
|
||||
discordWorldNameAsDiscordStatus: { __v_isRef: true, value: false }
|
||||
},
|
||||
showVRChatConfig: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
vi.mock('../../../../../stores', () => ({
|
||||
useDiscordPresenceSettingsStore: () => mocks.discordStore,
|
||||
useAdvancedSettingsStore: () => ({ showVRChatConfig: (...a) => mocks.showVRChatConfig(...a) })
|
||||
}));
|
||||
|
||||
vi.mock('../../SimpleSwitch.vue', () => ({
|
||||
default: {
|
||||
props: ['label', 'disabled'],
|
||||
emits: ['change'],
|
||||
template:
|
||||
'<div data-testid="simple-switch" :data-label="label" :data-disabled="disabled"><button class="emit-change" @click="$emit(\'change\', true)" /></div>'
|
||||
}
|
||||
}));
|
||||
|
||||
import DiscordPresenceTab from '../DiscordPresenceTab.vue';
|
||||
|
||||
describe('DiscordPresenceTab.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.discordStore.discordActive.value = true;
|
||||
mocks.discordStore.discordInstance.value = true;
|
||||
mocks.discordStore.setDiscordActive.mockClear();
|
||||
mocks.discordStore.saveDiscordOption.mockClear();
|
||||
mocks.showVRChatConfig.mockClear();
|
||||
});
|
||||
|
||||
it('opens VRChat config and handles switch changes', async () => {
|
||||
const wrapper = mount(DiscordPresenceTab);
|
||||
|
||||
const tooltipRow = wrapper
|
||||
.findAll('div.options-container-item')
|
||||
.find((node) => node.text().includes('view.settings.discord_presence.discord_presence.enable_tooltip'));
|
||||
await tooltipRow.trigger('click');
|
||||
|
||||
expect(mocks.showVRChatConfig).toHaveBeenCalledTimes(1);
|
||||
|
||||
await wrapper.findAll('.emit-change')[0].trigger('click');
|
||||
expect(mocks.discordStore.setDiscordActive).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.discordStore.saveDiscordOption).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('passes disabled state to dependent switches when discord is disabled', () => {
|
||||
mocks.discordStore.discordActive.value = false;
|
||||
const wrapper = mount(DiscordPresenceTab);
|
||||
|
||||
const worldIntegration = wrapper
|
||||
.findAll('[data-testid="simple-switch"]')
|
||||
.find((node) =>
|
||||
node.attributes('data-label')?.includes('world_integration')
|
||||
);
|
||||
|
||||
expect(worldIntegration?.attributes('data-disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
vi.mock('../../WristOverlaySettings.vue', () => ({
|
||||
default: {
|
||||
emits: ['open-feed-filters'],
|
||||
template:
|
||||
'<button data-testid="open-filters" @click="$emit(\'open-feed-filters\')">open</button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../dialogs/FeedFiltersDialog.vue', () => ({
|
||||
default: {
|
||||
props: ['feedFiltersDialogMode'],
|
||||
template: '<div data-testid="feed-dialog" :data-mode="feedFiltersDialogMode" />'
|
||||
}
|
||||
}));
|
||||
|
||||
import WristOverlayTab from '../WristOverlayTab.vue';
|
||||
|
||||
describe('WristOverlayTab.vue', () => {
|
||||
it('sets feed dialog mode to wrist when child emits open-feed-filters', async () => {
|
||||
const wrapper = mount(WristOverlayTab);
|
||||
|
||||
expect(wrapper.get('[data-testid="feed-dialog"]').attributes('data-mode')).toBe('');
|
||||
|
||||
await wrapper.get('[data-testid="open-filters"]').trigger('click');
|
||||
|
||||
expect(wrapper.get('[data-testid="feed-dialog"]').attributes('data-mode')).toBe('wrist');
|
||||
});
|
||||
});
|
||||
57
src/views/Settings/components/__tests__/SimpleSwitch.test.js
Normal file
57
src/views/Settings/components/__tests__/SimpleSwitch.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
vi.mock('../../../../components/ui/switch', () => ({
|
||||
Switch: {
|
||||
props: ['modelValue', 'disabled'],
|
||||
emits: ['update:modelValue'],
|
||||
template:
|
||||
'<button data-testid="switch" :data-disabled="disabled" @click="$emit(\'update:modelValue\', !modelValue)">switch</button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
Info: { template: '<i data-testid="info" />' }
|
||||
}));
|
||||
|
||||
import SimpleSwitch from '../SimpleSwitch.vue';
|
||||
|
||||
describe('SimpleSwitch.vue', () => {
|
||||
it('emits change when inner switch updates', async () => {
|
||||
const wrapper = mount(SimpleSwitch, {
|
||||
props: {
|
||||
label: 'Label',
|
||||
value: false
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<span data-testid="tooltip"><slot /></span>' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="switch"]').trigger('click');
|
||||
expect(wrapper.emitted('change')).toEqual([[true]]);
|
||||
});
|
||||
|
||||
it('applies long label style and renders tooltip', () => {
|
||||
const wrapper = mount(SimpleSwitch, {
|
||||
props: {
|
||||
label: 'Long',
|
||||
value: true,
|
||||
longLabel: true,
|
||||
tooltip: 'tip',
|
||||
disabled: true
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<span data-testid="tooltip"><slot /></span>' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.get('.name').attributes('style')).toContain('width: 300px');
|
||||
expect(wrapper.find('[data-testid="tooltip"]').exists()).toBe(true);
|
||||
expect(wrapper.get('[data-testid="switch"]').attributes('data-disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
notificationsStore: {
|
||||
openVR: { value: true },
|
||||
setOpenVR: vi.fn()
|
||||
},
|
||||
wristStore: {
|
||||
overlayWrist: { value: true },
|
||||
hidePrivateFromFeed: { value: false },
|
||||
openVRAlways: { value: false },
|
||||
overlaybutton: { value: false },
|
||||
overlayHand: { value: '1' },
|
||||
vrBackgroundEnabled: { value: false },
|
||||
minimalFeed: { value: false },
|
||||
hideDevicesFromFeed: { value: false },
|
||||
vrOverlayCpuUsage: { value: false },
|
||||
hideUptimeFromFeed: { value: false },
|
||||
pcUptimeOnFeed: { value: false },
|
||||
setOverlayWrist: vi.fn(),
|
||||
setHidePrivateFromFeed: vi.fn(),
|
||||
setOpenVRAlways: vi.fn(),
|
||||
setOverlaybutton: vi.fn(),
|
||||
setOverlayHand: vi.fn(),
|
||||
setVrBackgroundEnabled: vi.fn(),
|
||||
setMinimalFeed: vi.fn(),
|
||||
setHideDevicesFromFeed: vi.fn(),
|
||||
setVrOverlayCpuUsage: vi.fn(),
|
||||
setHideUptimeFromFeed: vi.fn(),
|
||||
setPcUptimeOnFeed: vi.fn()
|
||||
},
|
||||
saveOpenVROption: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useNotificationsSettingsStore: () => mocks.notificationsStore,
|
||||
useWristOverlaySettingsStore: () => mocks.wristStore,
|
||||
useVrStore: () => ({ saveOpenVROption: (...a) => mocks.saveOpenVROption(...a) })
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
props: ['disabled'],
|
||||
emits: ['click'],
|
||||
template:
|
||||
'<button data-testid="filters-btn" :disabled="disabled" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/ui/radio-group', () => ({
|
||||
RadioGroup: {
|
||||
props: ['modelValue', 'disabled'],
|
||||
emits: ['update:modelValue'],
|
||||
template:
|
||||
'<div data-testid="radio-group" :data-disabled="disabled"><button data-testid="radio-false" @click="$emit(\'update:modelValue\', \'false\')" /><button data-testid="radio-true" @click="$emit(\'update:modelValue\', \'true\')" /><slot /></div>'
|
||||
},
|
||||
RadioGroupItem: { template: '<div />' }
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/ui/toggle-group', () => ({
|
||||
ToggleGroup: {
|
||||
emits: ['update:model-value'],
|
||||
template:
|
||||
'<div data-testid="toggle-group"><button data-testid="toggle-right" @click="$emit(\'update:model-value\', \'2\')" /><slot /></div>'
|
||||
},
|
||||
ToggleGroupItem: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('../SimpleSwitch.vue', () => ({
|
||||
default: {
|
||||
props: ['label'],
|
||||
emits: ['change'],
|
||||
template:
|
||||
'<div data-testid="simple-switch" :data-label="label"><button class="emit-change" @click="$emit(\'change\', true)" /></div>'
|
||||
}
|
||||
}));
|
||||
|
||||
import WristOverlaySettings from '../WristOverlaySettings.vue';
|
||||
|
||||
describe('WristOverlaySettings.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.notificationsStore.openVR.value = true;
|
||||
mocks.wristStore.overlayWrist.value = true;
|
||||
mocks.wristStore.openVRAlways.value = false;
|
||||
mocks.wristStore.overlaybutton.value = false;
|
||||
mocks.notificationsStore.setOpenVR.mockClear();
|
||||
mocks.wristStore.setOpenVRAlways.mockClear();
|
||||
mocks.wristStore.setOverlaybutton.mockClear();
|
||||
mocks.wristStore.setOverlayHand.mockClear();
|
||||
mocks.saveOpenVROption.mockClear();
|
||||
});
|
||||
|
||||
it('emits open-feed-filters and handles switch/radio/toggle updates', async () => {
|
||||
const wrapper = mount(WristOverlaySettings);
|
||||
|
||||
await wrapper.get('[data-testid="filters-btn"]').trigger('click');
|
||||
expect(wrapper.emitted('open-feed-filters')).toBeTruthy();
|
||||
|
||||
await wrapper.findAll('.emit-change')[0].trigger('click');
|
||||
expect(mocks.notificationsStore.setOpenVR).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.saveOpenVROption).toHaveBeenCalled();
|
||||
|
||||
const radioGroups = wrapper.findAll('[data-testid="radio-group"]');
|
||||
await radioGroups[0].get('[data-testid="radio-true"]').trigger('click');
|
||||
expect(mocks.wristStore.setOpenVRAlways).toHaveBeenCalledTimes(1);
|
||||
|
||||
await radioGroups[1].get('[data-testid="radio-true"]').trigger('click');
|
||||
expect(mocks.wristStore.setOverlaybutton).toHaveBeenCalledTimes(1);
|
||||
|
||||
await wrapper.get('[data-testid="toggle-right"]').trigger('click');
|
||||
expect(mocks.wristStore.setOverlayHand).toHaveBeenCalledWith('2');
|
||||
});
|
||||
|
||||
it('does not toggle openVRAlways when the value is unchanged', async () => {
|
||||
mocks.wristStore.openVRAlways.value = true;
|
||||
const wrapper = mount(WristOverlaySettings);
|
||||
|
||||
const firstRadio = wrapper.findAll('[data-testid="radio-group"]')[0];
|
||||
await firstRadio.get('[data-testid="radio-true"]').trigger('click');
|
||||
|
||||
expect(mocks.wristStore.setOpenVRAlways).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
141
src/views/Settings/dialogs/__tests__/FeedFiltersDialog.test.js
Normal file
141
src/views/Settings/dialogs/__tests__/FeedFiltersDialog.test.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
sharedFeedFilters: {
|
||||
__v_isRef: true,
|
||||
value: {
|
||||
noty: { status: 'all' },
|
||||
wrist: { status: 'none' }
|
||||
}
|
||||
},
|
||||
photonLoggingEnabled: { __v_isRef: true, value: false },
|
||||
loadSharedFeed: vi.fn(),
|
||||
setString: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
usePhotonStore: () => ({ photonLoggingEnabled: mocks.photonLoggingEnabled }),
|
||||
useNotificationsSettingsStore: () => ({ sharedFeedFilters: mocks.sharedFeedFilters }),
|
||||
useSharedFeedStore: () => ({ loadSharedFeed: (...a) => mocks.loadSharedFeed(...a) })
|
||||
}));
|
||||
|
||||
vi.mock('../../../../services/config', () => ({
|
||||
default: {
|
||||
setString: (...a) => mocks.setString(...a)
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/constants', () => ({
|
||||
feedFiltersOptions: () => ({
|
||||
notyFeedFiltersOptions: [
|
||||
{
|
||||
key: 'status',
|
||||
name: 'Noty Status',
|
||||
options: [{ label: 'all', textKey: 'all' }]
|
||||
}
|
||||
],
|
||||
wristFeedFiltersOptions: [
|
||||
{
|
||||
key: 'status',
|
||||
name: 'Wrist Status',
|
||||
options: [{ label: 'none', textKey: 'none' }]
|
||||
}
|
||||
],
|
||||
photonFeedFiltersOptions: []
|
||||
}),
|
||||
sharedFeedFiltersDefaults: {
|
||||
noty: { status: 'default-noty' },
|
||||
wrist: { status: 'default-wrist' }
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/dialog', () => ({
|
||||
Dialog: {
|
||||
emits: ['update:open'],
|
||||
template:
|
||||
'<div><button data-testid="dialog-close" @click="$emit(\'update:open\', false)" /><slot /></div>'
|
||||
},
|
||||
DialogContent: { template: '<div><slot /></div>' },
|
||||
DialogHeader: { template: '<div><slot /></div>' },
|
||||
DialogTitle: { template: '<h2 data-testid="dialog-title"><slot /></h2>' },
|
||||
DialogFooter: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="btn" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/ui/toggle-group', () => ({
|
||||
ToggleGroup: {
|
||||
emits: ['update:model-value'],
|
||||
template:
|
||||
'<div><button data-testid="toggle-update" @click="$emit(\'update:model-value\', \'all\')" /><slot /></div>'
|
||||
},
|
||||
ToggleGroupItem: { template: '<button><slot /></button>' }
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
AlertTriangle: { template: '<i />' },
|
||||
Info: { template: '<i />' }
|
||||
}));
|
||||
|
||||
import FeedFiltersDialog from '../FeedFiltersDialog.vue';
|
||||
|
||||
describe('FeedFiltersDialog.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.sharedFeedFilters.value = {
|
||||
noty: { status: 'all' },
|
||||
wrist: { status: 'none' }
|
||||
};
|
||||
mocks.loadSharedFeed.mockClear();
|
||||
mocks.setString.mockClear();
|
||||
});
|
||||
|
||||
it('renders title by mode, saves filter change, and closes dialog', async () => {
|
||||
const wrapper = mount(FeedFiltersDialog, {
|
||||
props: {
|
||||
feedFiltersDialogMode: 'wrist'
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<span><slot /></span>' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.get('[data-testid="dialog-title"]').text()).toBe('dialog.shared_feed_filters.wrist');
|
||||
|
||||
await wrapper.get('[data-testid="toggle-update"]').trigger('click');
|
||||
expect(mocks.sharedFeedFilters.value.wrist.status).toBe('all');
|
||||
expect(mocks.setString).toHaveBeenCalledWith(
|
||||
'sharedFeedFilters',
|
||||
JSON.stringify(mocks.sharedFeedFilters.value)
|
||||
);
|
||||
expect(mocks.loadSharedFeed).toHaveBeenCalledTimes(1);
|
||||
|
||||
const closeButton = wrapper.findAll('[data-testid="btn"]')[1];
|
||||
await closeButton.trigger('click');
|
||||
expect(wrapper.emitted('update:feedFiltersDialogMode')).toEqual([['']]);
|
||||
});
|
||||
|
||||
it('resets noty filters to defaults', async () => {
|
||||
const wrapper = mount(FeedFiltersDialog, {
|
||||
props: {
|
||||
feedFiltersDialogMode: 'noty'
|
||||
}
|
||||
});
|
||||
|
||||
const resetButton = wrapper.findAll('[data-testid="btn"]')[0];
|
||||
await resetButton.trigger('click');
|
||||
|
||||
expect(mocks.sharedFeedFilters.value.noty).toEqual({ status: 'default-noty' });
|
||||
expect(mocks.setString).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
78
src/views/Sidebar/__tests__/Sidebar.test.js
Normal file
78
src/views/Sidebar/__tests__/Sidebar.test.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
openSearch: vi.fn(),
|
||||
markAllAsSeen: vi.fn(),
|
||||
refreshFriends: vi.fn(),
|
||||
hasUnseen: { value: true },
|
||||
centerOpen: { value: false }
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
vi.mock('@vueuse/core', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
useMagicKeys: () => ({}),
|
||||
whenever: vi.fn()
|
||||
};
|
||||
});
|
||||
vi.mock('../../../stores', () => ({
|
||||
useFriendStore: () => ({ friends: ref(new Map()), isRefreshFriendsLoading: ref(false), onlineFriendCount: ref(0) }),
|
||||
useGroupStore: () => ({ groupInstances: ref([]) }),
|
||||
useNotificationStore: () => ({ isNotificationCenterOpen: mocks.centerOpen, hasUnseenNotifications: mocks.hasUnseen, markAllAsSeen: (...a) => mocks.markAllAsSeen(...a) }),
|
||||
useAppearanceSettingsStore: () => ({ sidebarSortMethod1: ref(''), sidebarSortMethod2: ref(''), sidebarSortMethod3: ref(''), isSidebarGroupByInstance: ref(false), isHideFriendsInSameInstance: ref(false), isSidebarDivideByFriendGroup: ref(false), sidebarFavoriteGroups: ref([]), setSidebarSortMethod1: vi.fn(), setSidebarSortMethod2: vi.fn(), setSidebarSortMethod3: vi.fn(), setIsSidebarGroupByInstance: vi.fn(), setIsHideFriendsInSameInstance: vi.fn(), setIsSidebarDivideByFriendGroup: vi.fn(), setSidebarFavoriteGroups: vi.fn() }),
|
||||
useFavoriteStore: () => ({ favoriteFriendGroups: ref([]), localFriendFavoriteGroups: ref([]) })
|
||||
}));
|
||||
vi.mock('../../../stores/globalSearch', () => ({ useGlobalSearchStore: () => ({ open: (...a) => mocks.openSearch(...a) }) }));
|
||||
vi.mock('../../../coordinators/friendSyncCoordinator', () => ({ runRefreshFriendsListFlow: (...a) => mocks.refreshFriends(...a) }));
|
||||
vi.mock('../sidebarSettingsUtils', () => ({ normalizeFavoriteGroupsChange: (v) => v, resolveFavoriteGroups: (v) => v }));
|
||||
vi.mock('@/components/ui/button', () => ({ Button: { emits: ['click'], template: '<button data-testid="btn" @click="$emit(\'click\')"><slot /></button>' } }));
|
||||
vi.mock('@/components/ui/context-menu', () => ({ ContextMenu: { template: '<div><slot /></div>' }, ContextMenuTrigger: { template: '<div><slot /></div>' }, ContextMenuContent: { template: '<div><slot /></div>' }, ContextMenuItem: { emits: ['click'], template: '<button data-testid="ctx" @click="$emit(\'click\')"><slot /></button>' } }));
|
||||
vi.mock('@/components/ui/popover', () => ({ Popover: { template: '<div><slot /></div>' }, PopoverTrigger: { template: '<div><slot /></div>' }, PopoverContent: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('@/components/ui/select', () => ({ Select: { template: '<div><slot /></div>' }, SelectTrigger: { template: '<div><slot /></div>' }, SelectValue: { template: '<div><slot /></div>' }, SelectContent: { template: '<div><slot /></div>' }, SelectGroup: { template: '<div><slot /></div>' }, SelectItem: { template: '<div><slot /></div>' }, SelectSeparator: { template: '<hr />' } }));
|
||||
vi.mock('@/components/ui/field', () => ({ Field: { template: '<div><slot /></div>' }, FieldLabel: { template: '<div><slot /></div>' }, FieldContent: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('@/components/ui/tabs', () => ({ TabsUnderline: { template: '<div><slot name="friends" /><slot name="groups" /></div>' } }));
|
||||
vi.mock('@/components/ui/switch', () => ({ Switch: { template: '<div />' } }));
|
||||
vi.mock('@/components/ui/spinner', () => ({ Spinner: { template: '<div />' } }));
|
||||
vi.mock('@/components/ui/tooltip', () => ({ TooltipWrapper: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('@/components/ui/kbd', () => ({ Kbd: { template: '<kbd><slot /></kbd>' } }));
|
||||
vi.mock('@/components/ui/separator', () => ({ Separator: { template: '<hr />' } }));
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
Bell: { template: '<i />' },
|
||||
RefreshCw: { template: '<i />' },
|
||||
Search: { template: '<i />' },
|
||||
Settings: { template: '<i />' }
|
||||
}));
|
||||
vi.mock('../components/FriendsSidebar.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../components/GroupsSidebar.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../components/GroupOrderSheet.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../components/NotificationCenterSheet.vue', () => ({ default: { template: '<div />' } }));
|
||||
vi.mock('../../../components/GlobalSearchDialog.vue', () => ({ default: { template: '<div />' } }));
|
||||
|
||||
import Sidebar from '../Sidebar.vue';
|
||||
|
||||
describe('Sidebar.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.openSearch.mockClear();
|
||||
mocks.markAllAsSeen.mockClear();
|
||||
});
|
||||
|
||||
it('opens global search and marks notifications read', async () => {
|
||||
const wrapper = mount(Sidebar);
|
||||
const buttons = wrapper.findAll('button');
|
||||
for (const button of buttons) {
|
||||
await button.trigger('click');
|
||||
if (mocks.openSearch.mock.calls.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
await wrapper.get('[data-testid="ctx"]').trigger('click');
|
||||
|
||||
expect(mocks.openSearch).toHaveBeenCalled();
|
||||
expect(mocks.markAllAsSeen).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
145
src/views/Sidebar/components/__tests__/GroupsSidebar.test.js
Normal file
145
src/views/Sidebar/components/__tests__/GroupsSidebar.test.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
isAgeGatedInstancesVisible: { value: true },
|
||||
groupInstances: {
|
||||
value: [
|
||||
{
|
||||
group: {
|
||||
groupId: 'grp_1',
|
||||
name: 'Group One',
|
||||
iconUrl: 'https://example.com/icon.png'
|
||||
},
|
||||
instance: {
|
||||
id: 'inst_1',
|
||||
ownerId: 'usr_owner',
|
||||
userCount: 1,
|
||||
capacity: 16,
|
||||
location: 'wrld_1:123'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
sortGroupInstancesByInGame: (a, b) => a[0].group.name.localeCompare(b[0].group.name),
|
||||
showLaunchDialog: vi.fn(),
|
||||
checkCanInviteSelf: vi.fn(() => true),
|
||||
selfInvite: vi.fn().mockResolvedValue({}),
|
||||
showGroupDialog: vi.fn(),
|
||||
toastSuccess: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
|
||||
vi.mock('@tanstack/vue-virtual', () => ({
|
||||
useVirtualizer: (optionsRef) => ({
|
||||
value: {
|
||||
getVirtualItems: () =>
|
||||
Array.from({ length: optionsRef.value.count }, (_, index) => ({
|
||||
index,
|
||||
key: optionsRef.value.getItemKey(index),
|
||||
start: index * 52
|
||||
})),
|
||||
getTotalSize: () => optionsRef.value.count * 52,
|
||||
measure: vi.fn(),
|
||||
measureElement: vi.fn()
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useAppearanceSettingsStore: () => ({
|
||||
isAgeGatedInstancesVisible: mocks.isAgeGatedInstancesVisible
|
||||
}),
|
||||
useGroupStore: () => ({
|
||||
sortGroupInstancesByInGame: mocks.sortGroupInstancesByInGame,
|
||||
groupInstances: mocks.groupInstances
|
||||
}),
|
||||
useLaunchStore: () => ({ showLaunchDialog: (...a) => mocks.showLaunchDialog(...a) })
|
||||
}));
|
||||
|
||||
vi.mock('../../../../composables/useInviteChecks', () => ({
|
||||
useInviteChecks: () => ({ checkCanInviteSelf: (...a) => mocks.checkCanInviteSelf(...a) })
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/utils', () => ({
|
||||
convertFileUrlToImageUrl: (url) => `${url}?small`,
|
||||
parseLocation: (location) => ({
|
||||
isRealInstance: !!location,
|
||||
worldId: location.split(':')[0],
|
||||
instanceId: location.split(':')[1]
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/groupCoordinator', () => ({
|
||||
showGroupDialog: (...a) => mocks.showGroupDialog(...a)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../api', () => ({
|
||||
instanceRequest: {
|
||||
selfInvite: (...a) => mocks.selfInvite(...a)
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('vue-sonner', () => ({
|
||||
toast: {
|
||||
success: (...a) => mocks.toastSuccess(...a)
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/ui/context-menu', () => ({
|
||||
ContextMenu: { template: '<div><slot /></div>' },
|
||||
ContextMenuTrigger: { template: '<div><slot /></div>' },
|
||||
ContextMenuContent: { template: '<div><slot /></div>' },
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
props: ['disabled'],
|
||||
template:
|
||||
'<button data-testid="ctx-item" :disabled="disabled" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/BackToTop.vue', () => ({
|
||||
default: { template: '<div data-testid="back-to-top" />' }
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/Location.vue', () => ({
|
||||
default: { props: ['location'], template: '<span data-testid="location">{{ location }}</span>' }
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
ChevronDown: { template: '<i />' }
|
||||
}));
|
||||
|
||||
import GroupsSidebar from '../GroupsSidebar.vue';
|
||||
|
||||
describe('GroupsSidebar.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.showLaunchDialog.mockClear();
|
||||
mocks.selfInvite.mockClear();
|
||||
mocks.showGroupDialog.mockClear();
|
||||
mocks.toastSuccess.mockClear();
|
||||
});
|
||||
|
||||
it('renders group rows and handles launch/self-invite actions', async () => {
|
||||
const wrapper = mount(GroupsSidebar);
|
||||
|
||||
expect(wrapper.text()).toContain('Group One');
|
||||
|
||||
await wrapper.get('[data-testid="location"]').trigger('click');
|
||||
expect(mocks.showGroupDialog).toHaveBeenCalledWith('usr_owner');
|
||||
|
||||
const items = wrapper.findAll('[data-testid="ctx-item"]');
|
||||
await items[0].trigger('click');
|
||||
expect(mocks.showLaunchDialog).toHaveBeenCalledWith('wrld_1:123');
|
||||
|
||||
await items[1].trigger('click');
|
||||
await Promise.resolve();
|
||||
expect(mocks.selfInvite).toHaveBeenCalledWith({
|
||||
worldId: 'wrld_1',
|
||||
instanceId: '123'
|
||||
});
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith('message.invite.self_sent');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
followGroupEvent: vi.fn(async () => ({ json: { ok: true } })),
|
||||
showFullscreenImageDialog: vi.fn(),
|
||||
writeText: vi.fn(),
|
||||
toastSuccess: vi.fn()
|
||||
}));
|
||||
|
||||
Object.assign(globalThis, { navigator: { clipboard: { writeText: (...a) => mocks.writeText(...a) } } });
|
||||
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useGalleryStore: () => ({ showFullscreenImageDialog: (...a) => mocks.showFullscreenImageDialog(...a) }),
|
||||
useGroupStore: () => ({ cachedGroups: new Map([['grp_1', { name: 'Group One', bannerUrl: 'https://example.com/banner.png' }]]) })
|
||||
}));
|
||||
vi.mock('../../../../services/appConfig', () => ({ AppDebug: { endpointDomain: 'https://api.example.com' } }));
|
||||
vi.mock('../../../../shared/utils', () => ({ formatDateFilter: () => '12:00' }));
|
||||
vi.mock('../../../../api', () => ({ groupRequest: { followGroupEvent: (...a) => mocks.followGroupEvent(...a) } }));
|
||||
vi.mock('vue-sonner', () => ({ toast: { success: (...a) => mocks.toastSuccess(...a), error: vi.fn() } }));
|
||||
vi.mock('@/components/ui/popover', () => ({ Popover: { template: '<div><slot /></div>' }, PopoverTrigger: { template: '<div><slot /></div>' }, PopoverContent: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('@/components/ui/card', () => ({ Card: { template: '<div><slot /></div>' } }));
|
||||
vi.mock('@/components/ui/button', () => ({ Button: { emits: ['click'], template: '<button data-testid="btn" @click="$emit(\'click\')"><slot /></button>' } }));
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
Calendar: { template: '<i />' },
|
||||
Download: { template: '<i />' },
|
||||
Share2: { template: '<i />' },
|
||||
Star: { template: '<i />' }
|
||||
}));
|
||||
|
||||
import GroupCalendarEventCard from '../GroupCalendarEventCard.vue';
|
||||
|
||||
function mountCard() {
|
||||
return mount(GroupCalendarEventCard, {
|
||||
props: {
|
||||
event: { id: 'evt_1', ownerId: 'grp_1', title: 'Event One', startsAt: '2026-01-01', endsAt: '2026-01-01', accessType: 'public', category: 'social', interestedUserCount: 2, closeInstanceAfterEndMinutes: 30, createdAt: '2026-01-01', description: 'desc', imageUrl: '' },
|
||||
mode: 'timeline',
|
||||
isFollowing: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('GroupCalendarEventCard.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.followGroupEvent.mockClear();
|
||||
mocks.writeText.mockClear();
|
||||
});
|
||||
|
||||
it('copies event link and toggles follow', async () => {
|
||||
const wrapper = mountCard();
|
||||
const buttons = wrapper.findAll('[data-testid="btn"]');
|
||||
|
||||
await buttons[0].trigger('click');
|
||||
await buttons[1].trigger('click');
|
||||
await Promise.resolve();
|
||||
|
||||
expect(mocks.writeText).toHaveBeenCalledWith('https://vrchat.com/home/group/grp_1/calendar/evt_1');
|
||||
expect(mocks.followGroupEvent).toHaveBeenCalledWith({ groupId: 'grp_1', eventId: 'evt_1', isFollowing: true });
|
||||
expect(wrapper.emitted('update-following-calendar-data')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
159
src/views/Tools/dialogs/__tests__/AutoChangeStatusDialog.test.js
Normal file
159
src/views/Tools/dialogs/__tests__/AutoChangeStatusDialog.test.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
generalStore: {
|
||||
autoStateChangeEnabled: { __v_isRef: true, value: true },
|
||||
autoStateChangeAloneStatus: { __v_isRef: true, value: 'active' },
|
||||
autoStateChangeCompanyStatus: { __v_isRef: true, value: 'busy' },
|
||||
autoStateChangeInstanceTypes: { __v_isRef: true, value: ['invite'] },
|
||||
autoStateChangeNoFriends: { __v_isRef: true, value: false },
|
||||
autoStateChangeAloneDescEnabled: { __v_isRef: true, value: false },
|
||||
autoStateChangeAloneDesc: { __v_isRef: true, value: '' },
|
||||
autoStateChangeCompanyDescEnabled: { __v_isRef: true, value: false },
|
||||
autoStateChangeCompanyDesc: { __v_isRef: true, value: '' },
|
||||
autoStateChangeGroups: { __v_isRef: true, value: [] },
|
||||
autoAcceptInviteRequests: { __v_isRef: true, value: 'Off' },
|
||||
autoAcceptInviteGroups: { __v_isRef: true, value: [] },
|
||||
setAutoStateChangeEnabled: vi.fn(),
|
||||
setAutoStateChangeAloneStatus: vi.fn(),
|
||||
setAutoStateChangeCompanyStatus: vi.fn(),
|
||||
setAutoStateChangeInstanceTypes: vi.fn(),
|
||||
setAutoStateChangeNoFriends: vi.fn(),
|
||||
setAutoStateChangeAloneDescEnabled: vi.fn(),
|
||||
setAutoStateChangeAloneDesc: vi.fn(),
|
||||
setAutoStateChangeCompanyDescEnabled: vi.fn(),
|
||||
setAutoStateChangeCompanyDesc: vi.fn(),
|
||||
setAutoStateChangeGroups: vi.fn(),
|
||||
setAutoAcceptInviteRequests: vi.fn(),
|
||||
setAutoAcceptInviteGroups: vi.fn()
|
||||
},
|
||||
favoriteStore: {
|
||||
favoriteFriendGroups: {
|
||||
__v_isRef: true,
|
||||
value: [{ key: 'grp_a', displayName: 'Group A' }]
|
||||
},
|
||||
localFriendFavoriteGroups: { __v_isRef: true, value: ['Local A'] }
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('pinia', async (i) => ({ ...(await i()), storeToRefs: (s) => s }));
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useGeneralSettingsStore: () => mocks.generalStore,
|
||||
useFavoriteStore: () => mocks.favoriteStore
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/constants', () => ({
|
||||
accessTypeLocaleKeyMap: {
|
||||
invite: 'access.invite',
|
||||
invitePlus: 'access.invite_plus',
|
||||
friends: 'access.friends',
|
||||
friendsPlus: 'access.friends_plus',
|
||||
public: 'access.public',
|
||||
groupPublic: 'access.group_public',
|
||||
groupPlus: 'access.group_plus',
|
||||
groupMembers: 'access.group_members',
|
||||
group: 'access.group'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/dialog', () => ({
|
||||
Dialog: {
|
||||
emits: ['update:open'],
|
||||
template:
|
||||
'<div><button data-testid="dialog-close" @click="$emit(\'update:open\', false)" /><slot /></div>'
|
||||
},
|
||||
DialogContent: { template: '<div><slot /></div>' },
|
||||
DialogHeader: { template: '<div><slot /></div>' },
|
||||
DialogTitle: { template: '<h2><slot /></h2>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/field', () => ({
|
||||
Field: { template: '<div><slot /></div>' },
|
||||
FieldContent: { template: '<div><slot /></div>' },
|
||||
FieldGroup: { template: '<div><slot /></div>' },
|
||||
FieldLabel: { template: '<div><slot /></div>' },
|
||||
FieldSeparator: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/select', () => ({
|
||||
Select: { template: '<div><slot /></div>' },
|
||||
SelectContent: { template: '<div><slot /></div>' },
|
||||
SelectGroup: { template: '<div><slot /></div>' },
|
||||
SelectItem: { template: '<div><slot /></div>' },
|
||||
SelectSeparator: { template: '<div><slot /></div>' },
|
||||
SelectTrigger: { template: '<div><slot /></div>' },
|
||||
SelectValue: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/radio-group', () => ({
|
||||
RadioGroup: {
|
||||
emits: ['update:modelValue'],
|
||||
template:
|
||||
'<div data-testid="radio-group"><button class="emit-false" @click="$emit(\'update:modelValue\', \'false\')" /><button class="emit-true" @click="$emit(\'update:modelValue\', \'true\')" /><button class="emit-all" @click="$emit(\'update:modelValue\', \'All Favorites\')" /><slot /></div>'
|
||||
},
|
||||
RadioGroupItem: { template: '<div />' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/input', () => ({
|
||||
Input: { template: '<input />' }
|
||||
}));
|
||||
|
||||
vi.mock('../../../Settings/components/SimpleSwitch.vue', () => ({
|
||||
default: {
|
||||
props: ['label'],
|
||||
emits: ['change'],
|
||||
template:
|
||||
'<div data-testid="simple-switch" :data-label="label"><button class="emit-true" @click="$emit(\'change\', true)" /><button class="emit-false" @click="$emit(\'change\', false)" /></div>'
|
||||
}
|
||||
}));
|
||||
|
||||
import AutoChangeStatusDialog from '../AutoChangeStatusDialog.vue';
|
||||
|
||||
describe('AutoChangeStatusDialog.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.generalStore.autoStateChangeNoFriends.value = false;
|
||||
mocks.generalStore.autoAcceptInviteRequests.value = 'Off';
|
||||
mocks.generalStore.setAutoStateChangeNoFriends.mockClear();
|
||||
mocks.generalStore.setAutoAcceptInviteRequests.mockClear();
|
||||
});
|
||||
|
||||
it('emits close when dialog is closed', async () => {
|
||||
const wrapper = mount(AutoChangeStatusDialog, {
|
||||
props: { isAutoChangeStatusDialogVisible: true }
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="dialog-close"]').trigger('click');
|
||||
|
||||
expect(wrapper.emitted('close')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('handles auto accept switch and alone-condition radio changes', async () => {
|
||||
const wrapper = mount(AutoChangeStatusDialog, {
|
||||
props: { isAutoChangeStatusDialogVisible: true }
|
||||
});
|
||||
|
||||
const autoAcceptSwitch = wrapper
|
||||
.findAll('[data-testid="simple-switch"]')
|
||||
.find((node) =>
|
||||
node
|
||||
.attributes('data-label')
|
||||
?.includes('view.settings.general.automation.auto_invite_request_accept')
|
||||
);
|
||||
|
||||
await autoAcceptSwitch.find('.emit-false').trigger('click');
|
||||
expect(mocks.generalStore.setAutoAcceptInviteRequests).toHaveBeenCalledWith('Off');
|
||||
|
||||
await autoAcceptSwitch.find('.emit-true').trigger('click');
|
||||
expect(mocks.generalStore.setAutoAcceptInviteRequests).toHaveBeenCalledWith('All Favorites');
|
||||
|
||||
const noFriendsRadio = wrapper.findAll('[data-testid="radio-group"]')[0];
|
||||
await noFriendsRadio.find('.emit-false').trigger('click');
|
||||
expect(mocks.generalStore.setAutoStateChangeNoFriends).not.toHaveBeenCalled();
|
||||
|
||||
await noFriendsRadio.find('.emit-true').trigger('click');
|
||||
expect(mocks.generalStore.setAutoStateChangeNoFriends).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user