mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-25 09:43:49 +02:00
add test
This commit is contained in:
269
src/views/PlayerList/__tests__/PlayerList.test.js
Normal file
269
src/views/PlayerList/__tests__/PlayerList.test.js
Normal file
@@ -0,0 +1,269 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
randomUserColours: null,
|
||||
photonLoggingEnabled: null,
|
||||
chatboxUserBlacklist: null,
|
||||
lastLocation: null,
|
||||
currentInstanceLocation: null,
|
||||
currentInstanceWorld: null,
|
||||
currentInstanceUsersData: null,
|
||||
currentUser: null,
|
||||
saveChatboxUserBlacklist: vi.fn(),
|
||||
showUserDialog: vi.fn(),
|
||||
lookupUser: vi.fn(),
|
||||
showWorldDialog: vi.fn(),
|
||||
showFullscreenImageDialog: vi.fn(),
|
||||
getCurrentInstanceUserList: vi.fn(),
|
||||
tableSetOptions: vi.fn(),
|
||||
photonColumnToggleVisibility: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('pinia', () => ({
|
||||
storeToRefs: (store) => store
|
||||
}));
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores', () => ({
|
||||
useAppearanceSettingsStore: () => ({
|
||||
randomUserColours: mocks.randomUserColours
|
||||
}),
|
||||
usePhotonStore: () => ({
|
||||
photonLoggingEnabled: mocks.photonLoggingEnabled,
|
||||
chatboxUserBlacklist: mocks.chatboxUserBlacklist,
|
||||
saveChatboxUserBlacklist: (...args) => mocks.saveChatboxUserBlacklist(...args)
|
||||
}),
|
||||
useUserStore: () => ({
|
||||
currentUser: mocks.currentUser,
|
||||
showUserDialog: (...args) => mocks.showUserDialog(...args),
|
||||
lookupUser: (...args) => mocks.lookupUser(...args)
|
||||
}),
|
||||
useWorldStore: () => ({
|
||||
showWorldDialog: (...args) => mocks.showWorldDialog(...args)
|
||||
}),
|
||||
useLocationStore: () => ({
|
||||
lastLocation: mocks.lastLocation
|
||||
}),
|
||||
useInstanceStore: () => ({
|
||||
currentInstanceLocation: mocks.currentInstanceLocation,
|
||||
currentInstanceWorld: mocks.currentInstanceWorld,
|
||||
currentInstanceUsersData: mocks.currentInstanceUsersData,
|
||||
getCurrentInstanceUserList: (...args) => mocks.getCurrentInstanceUserList(...args)
|
||||
}),
|
||||
useGalleryStore: () => ({
|
||||
showFullscreenImageDialog: (...args) => mocks.showFullscreenImageDialog(...args)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../lib/table/useVrcxVueTable', () => ({
|
||||
useVrcxVueTable: () => ({
|
||||
table: {
|
||||
setOptions: (...args) => mocks.tableSetOptions(...args),
|
||||
getColumn: (id) =>
|
||||
id === 'photonId'
|
||||
? {
|
||||
toggleVisibility: (...args) => mocks.photonColumnToggleVisibility(...args)
|
||||
}
|
||||
: null,
|
||||
getRowModel: () => ({ rows: mocks.currentInstanceUsersData.value })
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../composables/useDataTableScrollHeight', () => ({
|
||||
useDataTableScrollHeight: () => ({
|
||||
tableStyle: {}
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../columns.jsx', () => ({
|
||||
createColumns: () => [{ id: 'photonId' }]
|
||||
}));
|
||||
|
||||
vi.mock('../../../shared/utils', () => ({
|
||||
commaNumber: (value) => String(value ?? ''),
|
||||
formatDateFilter: (value) => String(value ?? '')
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/data-table', () => ({
|
||||
DataTableLayout: {
|
||||
props: ['onRowClick'],
|
||||
template:
|
||||
'<div>' +
|
||||
'<button data-testid="row-click-with-id" @click="onRowClick?.({ original: { ref: { id: \'usr_1\', displayName: \'Alice\' } } })">row-id</button>' +
|
||||
'<button data-testid="row-click-without-id" @click="onRowClick?.({ original: { ref: { displayName: \'Bob\' } } })">row-no-id</button>' +
|
||||
'</div>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/badge', () => ({
|
||||
Badge: { template: '<span><slot /></span>' }
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/tooltip', () => ({
|
||||
TooltipWrapper: { template: '<div><slot /></div>' }
|
||||
}));
|
||||
|
||||
vi.mock('../../../components/LocationWorld.vue', () => ({
|
||||
default: { template: '<div />' }
|
||||
}));
|
||||
|
||||
vi.mock('../../../components/Timer.vue', () => ({
|
||||
default: { template: '<span />' }
|
||||
}));
|
||||
|
||||
vi.mock('../dialogs/ChatboxBlacklistDialog.vue', () => ({
|
||||
default: {
|
||||
props: ['chatboxBlacklistDialog'],
|
||||
emits: ['delete-chatbox-user-blacklist'],
|
||||
template:
|
||||
'<div data-testid="chatbox-dialog" :data-visible="String(chatboxBlacklistDialog.visible)">' +
|
||||
'<button data-testid="emit-delete-chatbox" @click="$emit(\'delete-chatbox-user-blacklist\', \'usr_blocked\')">delete</button>' +
|
||||
'</div>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
Apple: { template: '<span />' },
|
||||
Home: { template: '<span />' },
|
||||
Monitor: { template: '<span />' },
|
||||
Smartphone: { template: '<span />' }
|
||||
}));
|
||||
|
||||
import PlayerList from '../PlayerList.vue';
|
||||
|
||||
describe('PlayerList.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.randomUserColours = ref(false);
|
||||
mocks.photonLoggingEnabled = ref(false);
|
||||
mocks.chatboxUserBlacklist = ref(new Map([['usr_blocked', 'Blocked User']]));
|
||||
mocks.lastLocation = ref({
|
||||
playerList: new Set(),
|
||||
friendList: new Set(),
|
||||
date: null
|
||||
});
|
||||
mocks.currentInstanceLocation = ref({});
|
||||
mocks.currentInstanceWorld = ref({
|
||||
ref: {
|
||||
id: '',
|
||||
thumbnailImageUrl: '',
|
||||
imageUrl: '',
|
||||
name: '',
|
||||
authorId: '',
|
||||
authorName: '',
|
||||
releaseStatus: 'public',
|
||||
description: ''
|
||||
},
|
||||
fileAnalysis: {},
|
||||
isPC: false,
|
||||
isQuest: false,
|
||||
isIos: false
|
||||
});
|
||||
mocks.currentInstanceUsersData = ref([]);
|
||||
mocks.currentUser = ref({
|
||||
id: 'usr_me',
|
||||
$homeLocation: null
|
||||
});
|
||||
|
||||
mocks.saveChatboxUserBlacklist.mockReset();
|
||||
mocks.showUserDialog.mockReset();
|
||||
mocks.lookupUser.mockReset();
|
||||
mocks.showWorldDialog.mockReset();
|
||||
mocks.showFullscreenImageDialog.mockReset();
|
||||
mocks.getCurrentInstanceUserList.mockReset();
|
||||
mocks.tableSetOptions.mockReset();
|
||||
mocks.photonColumnToggleVisibility.mockReset();
|
||||
|
||||
mocks.saveChatboxUserBlacklist.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
test('loads current instance user list on mount and wires table options', () => {
|
||||
mount(PlayerList, {
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<div><slot /></div>' },
|
||||
LocationWorld: { template: '<div />' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(mocks.getCurrentInstanceUserList).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.tableSetOptions).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.photonColumnToggleVisibility).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test('row click opens user dialog when id exists, otherwise lookups user', async () => {
|
||||
const wrapper = mount(PlayerList, {
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<div><slot /></div>' },
|
||||
LocationWorld: { template: '<div />' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="row-click-with-id"]').trigger('click');
|
||||
await wrapper.get('[data-testid="row-click-without-id"]').trigger('click');
|
||||
|
||||
expect(mocks.showUserDialog).toHaveBeenCalledWith('usr_1');
|
||||
expect(mocks.lookupUser).toHaveBeenCalledWith({ displayName: 'Bob' });
|
||||
});
|
||||
|
||||
test('toggles photonId column visibility when photon logging changes', async () => {
|
||||
mount(PlayerList, {
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<div><slot /></div>' },
|
||||
LocationWorld: { template: '<div />' }
|
||||
}
|
||||
}
|
||||
});
|
||||
mocks.photonColumnToggleVisibility.mockClear();
|
||||
|
||||
mocks.photonLoggingEnabled.value = true;
|
||||
await nextTick();
|
||||
|
||||
expect(mocks.photonColumnToggleVisibility).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('opens chatbox blacklist dialog from photon event table', async () => {
|
||||
const wrapper = mount(PlayerList, {
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<div><slot /></div>' },
|
||||
LocationWorld: { template: '<div />' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.get('[data-testid="chatbox-dialog"]').attributes('data-visible')).toBe('false');
|
||||
wrapper.vm.showChatboxBlacklistDialog();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.get('[data-testid="chatbox-dialog"]').attributes('data-visible')).toBe('true');
|
||||
});
|
||||
|
||||
test('deletes chatbox blacklist user and refreshes list', async () => {
|
||||
const wrapper = mount(PlayerList, {
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<div><slot /></div>' },
|
||||
LocationWorld: { template: '<div />' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="emit-delete-chatbox"]').trigger('click');
|
||||
|
||||
expect(mocks.chatboxUserBlacklist.value.has('usr_blocked')).toBe(false);
|
||||
expect(mocks.saveChatboxUserBlacklist).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.getCurrentInstanceUserList).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
187
src/views/PlayerList/__tests__/columns.test.js
Normal file
187
src/views/PlayerList/__tests__/columns.test.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
openExternalLink: vi.fn(),
|
||||
userImage: vi.fn(),
|
||||
getFaviconUrl: vi.fn((link) => `https://icon/${encodeURIComponent(link)}`),
|
||||
sortAlphabetically: vi.fn(() => 123),
|
||||
onBlockChatbox: vi.fn(),
|
||||
onUnblockChatbox: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('../../../plugin', () => ({
|
||||
i18n: {
|
||||
global: {
|
||||
t: (key) => key
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../shared/utils', () => ({
|
||||
openExternalLink: (...args) => mocks.openExternalLink(...args),
|
||||
userImage: (...args) => mocks.userImage(...args),
|
||||
getFaviconUrl: (...args) => mocks.getFaviconUrl(...args),
|
||||
statusClass: () => 'status-online',
|
||||
languageClass: (lang) => `lang-${lang}`
|
||||
}));
|
||||
|
||||
vi.mock('../../../components/Timer.vue', () => ({ default: 'Timer' }));
|
||||
vi.mock('../../../components/ui/button', () => ({ Button: 'Button' }));
|
||||
vi.mock('../../../components/ui/tooltip', () => ({ TooltipWrapper: 'TooltipWrapper' }));
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
ArrowUpDown: 'ArrowUpDown',
|
||||
Monitor: 'Monitor',
|
||||
Smartphone: 'Smartphone',
|
||||
Apple: 'Apple',
|
||||
IdCard: 'IdCard'
|
||||
}));
|
||||
|
||||
import { createColumns } from '../columns.jsx';
|
||||
|
||||
function createElement(type, props, ...children) {
|
||||
return {
|
||||
type,
|
||||
props: props ?? {},
|
||||
children: children.flat()
|
||||
};
|
||||
}
|
||||
|
||||
function findNode(node, predicate) {
|
||||
if (!node) return null;
|
||||
if (Array.isArray(node)) {
|
||||
for (const item of node) {
|
||||
const result = findNode(item, predicate);
|
||||
if (result) return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (predicate(node)) return node;
|
||||
if (!node.children) return null;
|
||||
return findNode(node.children, predicate);
|
||||
}
|
||||
|
||||
function makeRow(overrides = {}) {
|
||||
const original = {
|
||||
displayName: 'Alice',
|
||||
photonId: 7,
|
||||
ref: {
|
||||
id: 'usr_1',
|
||||
displayName: 'Alice',
|
||||
$trustSortNum: 10,
|
||||
$trustLevel: 'Known',
|
||||
$trustClass: 'known',
|
||||
status: 'online',
|
||||
statusDescription: 'Online',
|
||||
$platform: 'standalonewindows',
|
||||
last_platform: 'standalonewindows',
|
||||
$languages: [],
|
||||
bioLinks: [],
|
||||
note: ''
|
||||
},
|
||||
...overrides
|
||||
};
|
||||
return {
|
||||
original,
|
||||
getValue: (key) => original[key]
|
||||
};
|
||||
}
|
||||
|
||||
describe('views/PlayerList/columns.jsx', () => {
|
||||
beforeEach(() => {
|
||||
globalThis.React = { createElement };
|
||||
mocks.openExternalLink.mockReset();
|
||||
mocks.userImage.mockReset();
|
||||
mocks.getFaviconUrl.mockClear();
|
||||
mocks.sortAlphabetically.mockClear();
|
||||
mocks.onBlockChatbox.mockReset();
|
||||
mocks.onUnblockChatbox.mockReset();
|
||||
});
|
||||
|
||||
test('displayName sorting uses injected sortAlphabetically helper', () => {
|
||||
const cols = createColumns({
|
||||
randomUserColours: { value: false, __v_isRef: true },
|
||||
chatboxUserBlacklist: { value: new Map(), __v_isRef: true },
|
||||
onBlockChatbox: mocks.onBlockChatbox,
|
||||
onUnblockChatbox: mocks.onUnblockChatbox,
|
||||
sortAlphabetically: mocks.sortAlphabetically
|
||||
});
|
||||
const displayNameCol = cols.find((c) => c.id === 'displayName');
|
||||
|
||||
const result = displayNameCol.sortingFn(
|
||||
{ original: { displayName: 'Alice' } },
|
||||
{ original: { displayName: 'Bob' } }
|
||||
);
|
||||
|
||||
expect(result).toBe(123);
|
||||
expect(mocks.sortAlphabetically).toHaveBeenCalledWith(
|
||||
{ displayName: 'Alice' },
|
||||
{ displayName: 'Bob' },
|
||||
'displayName'
|
||||
);
|
||||
});
|
||||
|
||||
test('photonId cell triggers block and unblock actions', () => {
|
||||
const row = makeRow();
|
||||
const cols = createColumns({
|
||||
randomUserColours: { value: false, __v_isRef: true },
|
||||
chatboxUserBlacklist: { value: new Map(), __v_isRef: true },
|
||||
onBlockChatbox: mocks.onBlockChatbox,
|
||||
onUnblockChatbox: mocks.onUnblockChatbox,
|
||||
sortAlphabetically: mocks.sortAlphabetically
|
||||
});
|
||||
const photonCol = cols.find((c) => c.id === 'photonId');
|
||||
const blockCell = photonCol.cell({ row });
|
||||
findNode(blockCell, (n) => n.type === 'button').props.onClick({ stopPropagation: vi.fn() });
|
||||
expect(mocks.onBlockChatbox).toHaveBeenCalledWith(row.original.ref);
|
||||
|
||||
const blockedMap = new Map([[row.original.ref.id, row.original.ref.displayName]]);
|
||||
const colsBlocked = createColumns({
|
||||
randomUserColours: { value: false, __v_isRef: true },
|
||||
chatboxUserBlacklist: { value: blockedMap, __v_isRef: true },
|
||||
onBlockChatbox: mocks.onBlockChatbox,
|
||||
onUnblockChatbox: mocks.onUnblockChatbox,
|
||||
sortAlphabetically: mocks.sortAlphabetically
|
||||
});
|
||||
const photonBlocked = colsBlocked.find((c) => c.id === 'photonId');
|
||||
const unblockCell = photonBlocked.cell({ row });
|
||||
findNode(unblockCell, (n) => n.type === 'button').props.onClick({ stopPropagation: vi.fn() });
|
||||
expect(mocks.onUnblockChatbox).toHaveBeenCalledWith('usr_1');
|
||||
});
|
||||
|
||||
test('icon sorting prefers higher weighted role flags', () => {
|
||||
const cols = createColumns({
|
||||
randomUserColours: { value: false, __v_isRef: true },
|
||||
chatboxUserBlacklist: { value: new Map(), __v_isRef: true },
|
||||
onBlockChatbox: mocks.onBlockChatbox,
|
||||
onUnblockChatbox: mocks.onUnblockChatbox,
|
||||
sortAlphabetically: mocks.sortAlphabetically
|
||||
});
|
||||
const iconCol = cols.find((c) => c.id === 'icon');
|
||||
|
||||
const master = { original: { isMaster: true } };
|
||||
const friend = { original: { isFriend: true } };
|
||||
|
||||
expect(iconCol.sortingFn(master, friend, 'icon')).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('bioLink cell opens external link when favicon is clicked', () => {
|
||||
const row = makeRow({
|
||||
ref: {
|
||||
...makeRow().original.ref,
|
||||
bioLinks: ['https://example.com']
|
||||
}
|
||||
});
|
||||
const cols = createColumns({
|
||||
randomUserColours: { value: false, __v_isRef: true },
|
||||
chatboxUserBlacklist: { value: new Map(), __v_isRef: true },
|
||||
onBlockChatbox: mocks.onBlockChatbox,
|
||||
onUnblockChatbox: mocks.onUnblockChatbox,
|
||||
sortAlphabetically: mocks.sortAlphabetically
|
||||
});
|
||||
const bioLinkCol = cols.find((c) => c.id === 'bioLink');
|
||||
const cell = bioLinkCol.cell({ row });
|
||||
findNode(cell, (n) => n.type === 'img').props.onClick({ stopPropagation: vi.fn() });
|
||||
|
||||
expect(mocks.openExternalLink).toHaveBeenCalledWith('https://example.com');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user