mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-25 01:33:51 +02:00
use oxfmt instead of prettier
This commit is contained in:
@@ -80,9 +80,19 @@ describe('graphLayoutWorker message protocol', () => {
|
||||
|
||||
harness.dispatch({
|
||||
requestId: 11,
|
||||
nodes: [{ id: 'n1', attributes: { x: 0, y: 0 } }, { id: 'n2', attributes: { x: 2, y: 2 } }],
|
||||
edges: [{ key: 'n1__n2', source: 'n1', target: 'n2', attributes: {} }],
|
||||
settings: { layoutIterations: 300, layoutSpacing: 60, deltaSpacing: 0, reinitialize: false }
|
||||
nodes: [
|
||||
{ id: 'n1', attributes: { x: 0, y: 0 } },
|
||||
{ id: 'n2', attributes: { x: 2, y: 2 } }
|
||||
],
|
||||
edges: [
|
||||
{ key: 'n1__n2', source: 'n1', target: 'n2', attributes: {} }
|
||||
],
|
||||
settings: {
|
||||
layoutIterations: 300,
|
||||
layoutSpacing: 60,
|
||||
deltaSpacing: 0,
|
||||
reinitialize: false
|
||||
}
|
||||
});
|
||||
|
||||
expect(harness.sent).toHaveLength(1);
|
||||
@@ -98,8 +108,15 @@ describe('graphLayoutWorker message protocol', () => {
|
||||
harness.dispatch({
|
||||
requestId: 12,
|
||||
nodes: [{ id: 'n1', attributes: { x: 0, y: 0 } }],
|
||||
edges: [{ key: 'n1__n2', source: 'n1', target: 'n2', attributes: {} }],
|
||||
settings: { layoutIterations: 300, layoutSpacing: 60, deltaSpacing: 0, reinitialize: false }
|
||||
edges: [
|
||||
{ key: 'n1__n2', source: 'n1', target: 'n2', attributes: {} }
|
||||
],
|
||||
settings: {
|
||||
layoutIterations: 300,
|
||||
layoutSpacing: 60,
|
||||
deltaSpacing: 0,
|
||||
reinitialize: false
|
||||
}
|
||||
});
|
||||
|
||||
expect(harness.sent).toHaveLength(1);
|
||||
|
||||
@@ -596,15 +596,15 @@
|
||||
const location = parseLocation(instanceData.location);
|
||||
|
||||
return `
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 6px;"></div>
|
||||
<div>
|
||||
<div>${name} #${location.instanceName} ${location.accessTypeName}</div>
|
||||
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
|
||||
<div>${timeString}</div>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 6px;"></div>
|
||||
<div>
|
||||
<div>${name} #${location.instanceName} ${location.accessTypeName}</div>
|
||||
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
|
||||
<div>${timeString}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
};
|
||||
|
||||
const format = dtHour12.value ? 'hh:mm A' : 'HH:mm';
|
||||
|
||||
@@ -359,15 +359,15 @@
|
||||
const color = param.color;
|
||||
|
||||
return `
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 6px;"></div>
|
||||
<div>
|
||||
<div>${instanceData.display_name} ${friendOrFavIcon(instanceData.display_name)}</div>
|
||||
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
|
||||
<div>${timeString}</div>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<div style="width: 10px; height: 55px; background-color: ${color}; margin-right: 6px;"></div>
|
||||
<div>
|
||||
<div>${instanceData.display_name} ${friendOrFavIcon(instanceData.display_name)}</div>
|
||||
<div>${formattedJoinDateTime} - ${formattedLeftDateTime}</div>
|
||||
<div>${timeString}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
};
|
||||
|
||||
const format = dtHour12.value ? 'hh:mm A' : 'HH:mm';
|
||||
|
||||
@@ -403,11 +403,7 @@
|
||||
const edgeCurvatureModel = computed({
|
||||
get: () => [layoutSettings.edgeCurvature],
|
||||
set: (value) => {
|
||||
const next = clampNumber(
|
||||
value?.[0] ?? layoutSettings.edgeCurvature,
|
||||
EDGE_CURVATURE_MIN,
|
||||
EDGE_CURVATURE_MAX
|
||||
);
|
||||
const next = clampNumber(value?.[0] ?? layoutSettings.edgeCurvature, EDGE_CURVATURE_MIN, EDGE_CURVATURE_MAX);
|
||||
layoutSettings.edgeCurvature = Number(next.toFixed(2));
|
||||
}
|
||||
});
|
||||
@@ -468,11 +464,7 @@
|
||||
layoutSettings.layoutIterations = clampNumber(iterations, LAYOUT_ITERATIONS_MIN, LAYOUT_ITERATIONS_MAX);
|
||||
layoutSettings.layoutSpacing = clampNumber(spacing, LAYOUT_SPACING_MIN, LAYOUT_SPACING_MAX);
|
||||
layoutSettings.edgeCurvature = clampNumber(curvature, EDGE_CURVATURE_MIN, EDGE_CURVATURE_MAX);
|
||||
layoutSettings.communitySeparation = clampNumber(
|
||||
separation,
|
||||
COMMUNITY_SEPARATION_MIN,
|
||||
COMMUNITY_SEPARATION_MAX
|
||||
);
|
||||
layoutSettings.communitySeparation = clampNumber(separation, COMMUNITY_SEPARATION_MIN, COMMUNITY_SEPARATION_MAX);
|
||||
lastLayoutSpacing = layoutSettings.layoutSpacing;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,12 @@ function runLayout(data) {
|
||||
graph.addNode(node.id, node.attributes);
|
||||
}
|
||||
for (const edge of edges) {
|
||||
graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
|
||||
graph.addEdgeWithKey(
|
||||
edge.key,
|
||||
edge.source,
|
||||
edge.target,
|
||||
edge.attributes
|
||||
);
|
||||
}
|
||||
|
||||
const reinitialize = settings.reinitialize ?? false;
|
||||
@@ -108,7 +113,9 @@ function runLayout(data) {
|
||||
LAYOUT_SPACING_MIN,
|
||||
LAYOUT_SPACING_MAX
|
||||
);
|
||||
const t = (spacing - LAYOUT_SPACING_MIN) / (LAYOUT_SPACING_MAX - LAYOUT_SPACING_MIN);
|
||||
const t =
|
||||
(spacing - LAYOUT_SPACING_MIN) /
|
||||
(LAYOUT_SPACING_MAX - LAYOUT_SPACING_MIN);
|
||||
const clampedT = clampNumber(t, 0, 1);
|
||||
const deltaSpacing = settings.deltaSpacing ?? 0;
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
:key="option.key"
|
||||
type="button"
|
||||
class="flex items-center gap-2 rounded-md border p-2 text-left text-sm hover:bg-accent"
|
||||
:class="option.key === currentPanelKey ? 'border-primary bg-primary/5 ring-1 ring-primary/40' : 'border-primary/20'"
|
||||
:class="
|
||||
option.key === currentPanelKey
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary/40'
|
||||
: 'border-primary/20'
|
||||
"
|
||||
@click="handleSelectWidget(option)">
|
||||
<i :class="option.icon" class="text-base"></i>
|
||||
<span>{{ t(option.labelKey) }}</span>
|
||||
@@ -36,7 +40,11 @@
|
||||
:key="option.key"
|
||||
type="button"
|
||||
class="flex items-center gap-2 rounded-md border p-2 text-left text-sm hover:bg-accent"
|
||||
:class="option.key === currentPanelKey ? 'border-primary bg-primary/5 ring-1 ring-primary/40' : ''"
|
||||
:class="
|
||||
option.key === currentPanelKey
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary/40'
|
||||
: ''
|
||||
"
|
||||
@click="emit('select', option.key)">
|
||||
<i :class="option.icon" class="text-base"></i>
|
||||
<span>{{ t(option.labelKey) }}</span>
|
||||
|
||||
@@ -29,10 +29,20 @@ export const panelComponentMap = {
|
||||
moderation: Moderation,
|
||||
notification: Notification,
|
||||
'my-avatars': MyAvatars,
|
||||
'charts-instance': defineAsyncComponent(() => import('../../Charts/components/InstanceActivity.vue')),
|
||||
'charts-mutual': defineAsyncComponent(() => import('../../Charts/components/MutualFriends.vue')),
|
||||
'charts-instance': defineAsyncComponent(
|
||||
() => import('../../Charts/components/InstanceActivity.vue')
|
||||
),
|
||||
'charts-mutual': defineAsyncComponent(
|
||||
() => import('../../Charts/components/MutualFriends.vue')
|
||||
),
|
||||
tools: Tools,
|
||||
'widget:feed': defineAsyncComponent(() => import('../widgets/FeedWidget.vue')),
|
||||
'widget:game-log': defineAsyncComponent(() => import('../widgets/GameLogWidget.vue')),
|
||||
'widget:instance': defineAsyncComponent(() => import('../widgets/InstanceWidget.vue'))
|
||||
'widget:feed': defineAsyncComponent(
|
||||
() => import('../widgets/FeedWidget.vue')
|
||||
),
|
||||
'widget:game-log': defineAsyncComponent(
|
||||
() => import('../widgets/GameLogWidget.vue')
|
||||
),
|
||||
'widget:instance': defineAsyncComponent(
|
||||
() => import('../widgets/InstanceWidget.vue')
|
||||
)
|
||||
};
|
||||
|
||||
@@ -160,15 +160,7 @@
|
||||
import WidgetHeader from './WidgetHeader.vue';
|
||||
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
||||
|
||||
const GAMELOG_TYPES = [
|
||||
'Location',
|
||||
'OnPlayerJoined',
|
||||
'OnPlayerLeft',
|
||||
'VideoPlay',
|
||||
'PortalSpawn',
|
||||
'Event',
|
||||
'External'
|
||||
];
|
||||
const GAMELOG_TYPES = ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'VideoPlay', 'PortalSpawn', 'Event', 'External'];
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
|
||||
@@ -668,9 +668,7 @@
|
||||
if (!activeRemoteGroup.value || !currentRemoteFavorites.value.length) {
|
||||
return false;
|
||||
}
|
||||
return currentRemoteFavorites.value
|
||||
.map((fav) => fav.id)
|
||||
.every((id) => selectedFavoriteAvatars.value.includes(id));
|
||||
return currentRemoteFavorites.value.map((fav) => fav.id).every((id) => selectedFavoriteAvatars.value.includes(id));
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -1226,7 +1224,7 @@
|
||||
modalStore
|
||||
.confirm({
|
||||
description: `Are you sure you want to unfavorite ${total} favorites?
|
||||
This action cannot be undone.`,
|
||||
This action cannot be undone.`,
|
||||
title: `Delete ${total} favorites?`
|
||||
})
|
||||
.then(({ ok }) => {
|
||||
|
||||
@@ -553,9 +553,7 @@
|
||||
if (!activeRemoteGroup.value || !currentFriendFavorites.value.length) {
|
||||
return false;
|
||||
}
|
||||
return currentFriendFavorites.value
|
||||
.map((fav) => fav.id)
|
||||
.every((id) => selectedFavoriteFriends.value.includes(id));
|
||||
return currentFriendFavorites.value.map((fav) => fav.id).every((id) => selectedFavoriteFriends.value.includes(id));
|
||||
});
|
||||
|
||||
watch(
|
||||
|
||||
@@ -694,9 +694,7 @@
|
||||
if (!activeRemoteGroup.value || !currentRemoteFavorites.value.length) {
|
||||
return false;
|
||||
}
|
||||
return currentRemoteFavorites.value
|
||||
.map((fav) => fav.id)
|
||||
.every((id) => selectedFavoriteWorlds.value.includes(id));
|
||||
return currentRemoteFavorites.value.map((fav) => fav.id).every((id) => selectedFavoriteWorlds.value.includes(id));
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -838,7 +836,7 @@
|
||||
modalStore
|
||||
.confirm({
|
||||
description: `Are you sure you want to unfavorite ${total} favorites?
|
||||
This action cannot be undone.`,
|
||||
This action cannot be undone.`,
|
||||
title: `Delete ${total} favorites?`
|
||||
})
|
||||
.then(({ ok }) => {
|
||||
|
||||
@@ -35,11 +35,13 @@ vi.mock('../../../../stores', () => ({
|
||||
|
||||
vi.mock('../../../../coordinators/avatarCoordinator', () => ({
|
||||
showAvatarDialog: (...args) => mocks.showAvatarDialog(...args),
|
||||
selectAvatarWithConfirmation: (...args) => mocks.selectAvatarWithConfirmation(...args)
|
||||
selectAvatarWithConfirmation: (...args) =>
|
||||
mocks.selectAvatarWithConfirmation(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/favoriteCoordinator', () => ({
|
||||
removeLocalAvatarFavorite: (...args) => mocks.removeLocalAvatarFavorite(...args)
|
||||
removeLocalAvatarFavorite: (...args) =>
|
||||
mocks.removeLocalAvatarFavorite(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../api', () => ({
|
||||
@@ -51,7 +53,8 @@ vi.mock('../../../../api', () => ({
|
||||
vi.mock('@/components/ui/item', () => ({
|
||||
Item: {
|
||||
emits: ['click'],
|
||||
template: '<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
template:
|
||||
'<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
},
|
||||
ItemActions: { template: '<div><slot /></div>' },
|
||||
ItemMedia: { template: '<div><slot /></div>' },
|
||||
@@ -64,15 +67,20 @@ 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" />'
|
||||
template:
|
||||
'<img data-testid="avatar-image" :src="src" :class="$attrs.class" />'
|
||||
},
|
||||
AvatarFallback: { template: '<span data-testid="avatar-fallback" :class="$attrs.class"><slot /></span>' }
|
||||
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>'
|
||||
template:
|
||||
'<button data-testid="button" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -92,7 +100,8 @@ vi.mock('@/components/ui/context-menu', () => ({
|
||||
ContextMenuSeparator: { template: '<hr />' },
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -103,7 +112,8 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
DropdownMenuSeparator: { template: '<hr />' },
|
||||
DropdownMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -175,16 +185,26 @@ describe('FavoritesAvatarItem.vue', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
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');
|
||||
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', () => {
|
||||
@@ -200,8 +220,12 @@ describe('FavoritesAvatarItem.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="avatar-image"]').exists()).toBe(false);
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').text()).toContain('B');
|
||||
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 () => {
|
||||
@@ -217,7 +241,10 @@ describe('FavoritesAvatarItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.delete_tooltip');
|
||||
|
||||
expect(mocks.removeLocalAvatarFavorite).toHaveBeenCalledWith('avtr_local', 'LocalGroup');
|
||||
expect(mocks.removeLocalAvatarFavorite).toHaveBeenCalledWith(
|
||||
'avtr_local',
|
||||
'LocalGroup'
|
||||
);
|
||||
expect(mocks.deleteFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -226,7 +253,9 @@ describe('FavoritesAvatarItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.unfavorite_tooltip');
|
||||
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({ objectId: 'avtr_1' });
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({
|
||||
objectId: 'avtr_1'
|
||||
});
|
||||
expect(mocks.removeLocalAvatarFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,13 +33,15 @@ vi.mock('../../../../stores', () => ({
|
||||
|
||||
vi.mock('../../../../coordinators/avatarCoordinator', () => ({
|
||||
showAvatarDialog: (...args) => mocks.showAvatarDialog(...args),
|
||||
selectAvatarWithConfirmation: (...args) => mocks.selectAvatarWithConfirmation(...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>'
|
||||
template:
|
||||
'<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
},
|
||||
ItemActions: { template: '<div><slot /></div>' },
|
||||
ItemMedia: { template: '<div><slot /></div>' },
|
||||
@@ -52,15 +54,20 @@ 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" />'
|
||||
template:
|
||||
'<img data-testid="avatar-image" :src="src" :class="$attrs.class" />'
|
||||
},
|
||||
AvatarFallback: { template: '<span data-testid="avatar-fallback" :class="$attrs.class"><slot /></span>' }
|
||||
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>'
|
||||
template:
|
||||
'<button data-testid="button" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -71,7 +78,8 @@ vi.mock('@/components/ui/context-menu', () => ({
|
||||
ContextMenuSeparator: { template: '<hr />' },
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -82,7 +90,8 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
DropdownMenuSeparator: { template: '<hr />' },
|
||||
DropdownMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -142,16 +151,26 @@ describe('FavoritesAvatarLocalHistoryItem.vue', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
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');
|
||||
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', () => {
|
||||
@@ -163,8 +182,12 @@ describe('FavoritesAvatarLocalHistoryItem.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="avatar-image"]').exists()).toBe(false);
|
||||
expect(wrapper.get('[data-testid="avatar-fallback"]').text()).toContain('C');
|
||||
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 () => {
|
||||
@@ -172,6 +195,8 @@ describe('FavoritesAvatarLocalHistoryItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.select_avatar_tooltip');
|
||||
|
||||
expect(mocks.selectAvatarWithConfirmation).toHaveBeenCalledWith('avtr_hist_1');
|
||||
expect(mocks.selectAvatarWithConfirmation).toHaveBeenCalledWith(
|
||||
'avtr_hist_1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,8 @@ vi.mock('../../../../api', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/favoriteCoordinator', () => ({
|
||||
removeLocalFriendFavorite: (...args) => mocks.removeLocalFriendFavorite(...args)
|
||||
removeLocalFriendFavorite: (...args) =>
|
||||
mocks.removeLocalFriendFavorite(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/userCoordinator', () => ({
|
||||
@@ -100,7 +101,11 @@ vi.mock('../../../../composables/useUserDisplay', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/utils', () => ({
|
||||
parseLocation: () => ({ worldId: 'wrld_123', instanceId: '123', tag: '123~private' }),
|
||||
parseLocation: () => ({
|
||||
worldId: 'wrld_123',
|
||||
instanceId: '123',
|
||||
tag: '123~private'
|
||||
}),
|
||||
isRealInstance: (...args) => mocks.isRealInstance(...args)
|
||||
}));
|
||||
|
||||
@@ -113,7 +118,8 @@ vi.mock('../../../../components/Location.vue', () => ({
|
||||
vi.mock('@/components/ui/item', () => ({
|
||||
Item: {
|
||||
emits: ['click'],
|
||||
template: '<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
template:
|
||||
'<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
},
|
||||
ItemActions: { template: '<div><slot /></div>' },
|
||||
ItemMedia: { template: '<div><slot /></div>' },
|
||||
@@ -131,7 +137,8 @@ vi.mock('@/components/ui/avatar', () => ({
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="btn" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="btn" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -151,7 +158,8 @@ vi.mock('@/components/ui/context-menu', () => ({
|
||||
ContextMenuSeparator: { template: '<hr />' },
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="context-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="context-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -162,7 +170,8 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
DropdownMenuSeparator: { template: '<hr />' },
|
||||
DropdownMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="dropdown-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="dropdown-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -244,7 +253,11 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
expect.arrayContaining([
|
||||
'favorites-item',
|
||||
'hover:bg-muted',
|
||||
'x-hover-list'
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
@@ -261,7 +274,10 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.delete_tooltip');
|
||||
|
||||
expect(mocks.removeLocalFriendFavorite).toHaveBeenCalledWith('usr_1', 'Local');
|
||||
expect(mocks.removeLocalFriendFavorite).toHaveBeenCalledWith(
|
||||
'usr_1',
|
||||
'Local'
|
||||
);
|
||||
expect(mocks.deleteFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -270,7 +286,9 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.unfavorite_tooltip');
|
||||
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({ objectId: 'usr_1' });
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({
|
||||
objectId: 'usr_1'
|
||||
});
|
||||
expect(mocks.removeLocalFriendFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -279,8 +297,12 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
|
||||
expect(wrapper.text()).toContain('dialog.user.actions.request_invite');
|
||||
expect(wrapper.text()).toContain('dialog.user.actions.invite');
|
||||
expect(wrapper.text()).toContain('dialog.user.info.launch_invite_tooltip');
|
||||
expect(wrapper.text()).toContain('dialog.user.info.self_invite_tooltip');
|
||||
expect(wrapper.text()).toContain(
|
||||
'dialog.user.info.launch_invite_tooltip'
|
||||
);
|
||||
expect(wrapper.text()).toContain(
|
||||
'dialog.user.info.self_invite_tooltip'
|
||||
);
|
||||
});
|
||||
|
||||
it('hides invite/join actions when offline', () => {
|
||||
@@ -298,9 +320,15 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.text()).not.toContain('dialog.user.actions.request_invite');
|
||||
expect(wrapper.text()).not.toContain('dialog.user.info.launch_invite_tooltip');
|
||||
expect(wrapper.text()).not.toContain('dialog.user.info.self_invite_tooltip');
|
||||
expect(wrapper.text()).not.toContain(
|
||||
'dialog.user.actions.request_invite'
|
||||
);
|
||||
expect(wrapper.text()).not.toContain(
|
||||
'dialog.user.info.launch_invite_tooltip'
|
||||
);
|
||||
expect(wrapper.text()).not.toContain(
|
||||
'dialog.user.info.self_invite_tooltip'
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers request invite action', async () => {
|
||||
@@ -308,7 +336,10 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'dialog.user.actions.request_invite');
|
||||
|
||||
expect(mocks.sendRequestInvite).toHaveBeenCalledWith({ platform: 'standalonewindows' }, 'usr_1');
|
||||
expect(mocks.sendRequestInvite).toHaveBeenCalledWith(
|
||||
{ platform: 'standalonewindows' },
|
||||
'usr_1'
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers join action', async () => {
|
||||
@@ -316,6 +347,8 @@ describe('FavoritesFriendItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'dialog.user.info.launch_invite_tooltip');
|
||||
|
||||
expect(mocks.showLaunchDialog).toHaveBeenCalledWith('wrld_aaa:1~private');
|
||||
expect(mocks.showLaunchDialog).toHaveBeenCalledWith(
|
||||
'wrld_aaa:1~private'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,8 @@ vi.mock('@/components/ui/context-menu', () => ({
|
||||
},
|
||||
ContextMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="ctx-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -60,14 +61,16 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
},
|
||||
DropdownMenuItem: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="dd-item" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/item', () => ({
|
||||
Item: {
|
||||
emits: ['click'],
|
||||
template: '<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
template:
|
||||
'<div data-testid="item" @click="$emit(\'click\', $event)"><slot /></div>'
|
||||
},
|
||||
ItemActions: { template: '<div><slot /></div>' },
|
||||
ItemMedia: { template: '<div><slot /></div>' },
|
||||
@@ -82,13 +85,16 @@ vi.mock('@/components/ui/avatar', () => ({
|
||||
props: ['src'],
|
||||
template: '<img data-testid="avatar-image" :src="src" />'
|
||||
},
|
||||
AvatarFallback: { template: '<span data-testid="avatar-fallback"><slot /></span>' }
|
||||
AvatarFallback: {
|
||||
template: '<span data-testid="avatar-fallback"><slot /></span>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="btn" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="btn" @click="$emit(\'click\', $event)"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -126,7 +132,8 @@ vi.mock('../../../../api', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/inviteCoordinator', () => ({
|
||||
runNewInstanceSelfInviteFlow: (...args) => mocks.newInstanceSelfInvite(...args)
|
||||
runNewInstanceSelfInviteFlow: (...args) =>
|
||||
mocks.newInstanceSelfInvite(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/worldCoordinator', () => ({
|
||||
@@ -134,7 +141,8 @@ vi.mock('../../../../coordinators/worldCoordinator', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../../../coordinators/favoriteCoordinator', () => ({
|
||||
removeLocalWorldFavorite: (...args) => mocks.removeLocalWorldFavorite(...args)
|
||||
removeLocalWorldFavorite: (...args) =>
|
||||
mocks.removeLocalWorldFavorite(...args)
|
||||
}));
|
||||
|
||||
import FavoritesWorldItem from '../FavoritesWorldItem.vue';
|
||||
@@ -201,7 +209,9 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
|
||||
expect(text).toContain('common.actions.view_details');
|
||||
expect(text).toContain('dialog.world.actions.new_instance');
|
||||
expect(text).toContain('dialog.world.actions.new_instance_and_self_invite');
|
||||
expect(text).toContain(
|
||||
'dialog.world.actions.new_instance_and_self_invite'
|
||||
);
|
||||
expect(text).toContain('view.favorite.edit_favorite_tooltip');
|
||||
expect(text).toContain('view.favorite.unfavorite_tooltip');
|
||||
});
|
||||
@@ -219,7 +229,10 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.edit_favorite_tooltip');
|
||||
|
||||
expect(mocks.showFavoriteDialog).toHaveBeenCalledWith('world', 'wrld_default');
|
||||
expect(mocks.showFavoriteDialog).toHaveBeenCalledWith(
|
||||
'world',
|
||||
'wrld_default'
|
||||
);
|
||||
});
|
||||
|
||||
it('emits toggle-select in edit mode for remote favorites', async () => {
|
||||
@@ -258,7 +271,11 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
expect(wrapper.get('[data-testid="item"]').classes()).toEqual(
|
||||
expect.arrayContaining(['favorites-item', 'hover:bg-muted', 'x-hover-list'])
|
||||
expect.arrayContaining([
|
||||
'favorites-item',
|
||||
'hover:bg-muted',
|
||||
'x-hover-list'
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
@@ -275,9 +292,15 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
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 () => {
|
||||
@@ -292,7 +315,10 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.delete_tooltip');
|
||||
|
||||
expect(mocks.removeLocalWorldFavorite).toHaveBeenCalledWith('wrld_local_1', 'LocalGroup');
|
||||
expect(mocks.removeLocalWorldFavorite).toHaveBeenCalledWith(
|
||||
'wrld_local_1',
|
||||
'LocalGroup'
|
||||
);
|
||||
expect(mocks.deleteFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -301,7 +327,9 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
|
||||
await clickMenuItem(wrapper, 'view.favorite.unfavorite_tooltip');
|
||||
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({ objectId: 'wrld_default' });
|
||||
expect(mocks.deleteFavorite).toHaveBeenCalledWith({
|
||||
objectId: 'wrld_default'
|
||||
});
|
||||
expect(mocks.removeLocalWorldFavorite).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -309,9 +337,14 @@ describe('FavoritesWorldItem.vue', () => {
|
||||
const wrapper = mountItem();
|
||||
|
||||
await clickMenuItem(wrapper, 'dialog.world.actions.new_instance');
|
||||
await clickMenuItem(wrapper, 'dialog.world.actions.new_instance_and_self_invite');
|
||||
await clickMenuItem(
|
||||
wrapper,
|
||||
'dialog.world.actions.new_instance_and_self_invite'
|
||||
);
|
||||
|
||||
expect(mocks.createNewInstance).toHaveBeenCalledWith('wrld_default');
|
||||
expect(mocks.newInstanceSelfInvite).toHaveBeenCalledWith('wrld_default');
|
||||
expect(mocks.newInstanceSelfInvite).toHaveBeenCalledWith(
|
||||
'wrld_default'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,7 +46,9 @@ describe('useFavoritesCardScaling', () => {
|
||||
|
||||
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(
|
||||
Number(style['--favorites-grid-columns'])
|
||||
).toBeGreaterThanOrEqual(1);
|
||||
expect(mocks.setString).toHaveBeenCalledWith('scale-key', '0.8');
|
||||
expect(mocks.setString).toHaveBeenCalledWith('spacing-key', '1.2');
|
||||
});
|
||||
|
||||
@@ -38,7 +38,9 @@ function createPanel(options = {}) {
|
||||
|
||||
describe('useFavoritesGroupPanel', () => {
|
||||
it('selects remote placeholder by default when remote groups are unresolved', () => {
|
||||
const placeholders = [{ key: 'avatar:avatars1', displayName: 'Group 1' }];
|
||||
const placeholders = [
|
||||
{ key: 'avatar:avatars1', displayName: 'Group 1' }
|
||||
];
|
||||
const { panel, clearSelection } = createPanel({ placeholders });
|
||||
|
||||
panel.ensureSelectedGroup();
|
||||
|
||||
@@ -16,7 +16,9 @@ describe('useFavoritesLocalGroups', () => {
|
||||
await Promise.resolve();
|
||||
|
||||
expect(createGroup).toHaveBeenCalledWith('Local A');
|
||||
expect(selectGroup).toHaveBeenCalledWith('local', 'Local A', { userInitiated: true });
|
||||
expect(selectGroup).toHaveBeenCalledWith('local', 'Local A', {
|
||||
userInitiated: true
|
||||
});
|
||||
expect(api.isCreatingLocalGroup.value).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ describe('useFavoritesSplitter', () => {
|
||||
|
||||
it('persists layout size while dragging', () => {
|
||||
const api = mountComposable();
|
||||
api.splitterGroupRef.value = { getBoundingClientRect: () => ({ width: 1200 }) };
|
||||
api.splitterGroupRef.value = {
|
||||
getBoundingClientRect: () => ({ width: 1200 })
|
||||
};
|
||||
|
||||
api.setDragging(true);
|
||||
api.handleLayout([25, 75]);
|
||||
@@ -52,7 +54,9 @@ describe('useFavoritesSplitter', () => {
|
||||
|
||||
it('ignores layout updates when not dragging', () => {
|
||||
const api = mountComposable();
|
||||
api.splitterGroupRef.value = { getBoundingClientRect: () => ({ width: 1200 }) };
|
||||
api.splitterGroupRef.value = {
|
||||
getBoundingClientRect: () => ({ width: 1200 })
|
||||
};
|
||||
|
||||
api.handleLayout([20, 80]);
|
||||
|
||||
|
||||
@@ -162,9 +162,7 @@
|
||||
|
||||
const tableStyle = { maxHeight: '400px' };
|
||||
|
||||
const rows = computed(() =>
|
||||
Array.isArray(avatarImportTable.value?.data) ? avatarImportTable.value.data.slice() : []
|
||||
);
|
||||
const rows = computed(() => (Array.isArray(avatarImportTable.value?.data) ? avatarImportTable.value.data.slice() : []));
|
||||
|
||||
const columns = computed(() =>
|
||||
createColumns({
|
||||
|
||||
@@ -167,9 +167,7 @@
|
||||
|
||||
const tableStyle = { maxHeight: '400px' };
|
||||
|
||||
const rows = computed(() =>
|
||||
Array.isArray(friendImportTable.value?.data) ? friendImportTable.value.data.slice() : []
|
||||
);
|
||||
const rows = computed(() => (Array.isArray(friendImportTable.value?.data) ? friendImportTable.value.data.slice() : []));
|
||||
|
||||
const columns = computed(() =>
|
||||
createColumns({
|
||||
@@ -207,8 +205,7 @@
|
||||
if (value) {
|
||||
clearFriendImportTable();
|
||||
resetFriendImport();
|
||||
friendImportFavoriteGroupSelection.value =
|
||||
friendImportDialog.value.friendImportFavoriteGroup?.name ?? '';
|
||||
friendImportFavoriteGroupSelection.value = friendImportDialog.value.friendImportFavoriteGroup?.name ?? '';
|
||||
if (friendImportDialogInput.value) {
|
||||
friendImportDialog.value.input = friendImportDialogInput.value;
|
||||
processFriendImportList();
|
||||
|
||||
@@ -88,13 +88,8 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
favoriteWorlds,
|
||||
favoriteWorldGroups,
|
||||
localWorldFavorites,
|
||||
localWorldFavoritesList,
|
||||
localWorldFavoriteGroups
|
||||
} = storeToRefs(useFavoriteStore());
|
||||
const { favoriteWorlds, favoriteWorldGroups, localWorldFavorites, localWorldFavoritesList, localWorldFavoriteGroups } =
|
||||
storeToRefs(useFavoriteStore());
|
||||
const { localWorldFavGroupLength } = useFavoriteStore();
|
||||
const { cachedWorlds } = useWorldStore();
|
||||
|
||||
|
||||
@@ -168,9 +168,7 @@
|
||||
|
||||
const tableStyle = { maxHeight: '400px' };
|
||||
|
||||
const rows = computed(() =>
|
||||
Array.isArray(worldImportTable.value?.data) ? worldImportTable.value.data.slice() : []
|
||||
);
|
||||
const rows = computed(() => (Array.isArray(worldImportTable.value?.data) ? worldImportTable.value.data.slice() : []));
|
||||
|
||||
const columns = computed(() =>
|
||||
createColumns({
|
||||
|
||||
@@ -164,7 +164,9 @@ vi.mock('lucide-vue-next', () => ({
|
||||
import Feed from '../Feed.vue';
|
||||
|
||||
function clickButtonByText(wrapper, text) {
|
||||
const button = wrapper.findAll('button').find((node) => node.text().includes(text));
|
||||
const button = wrapper
|
||||
.findAll('button')
|
||||
.find((node) => node.text().includes(text));
|
||||
if (!button) {
|
||||
throw new Error(`Cannot find button with text: ${text}`);
|
||||
}
|
||||
@@ -255,6 +257,8 @@ describe('Feed.vue', () => {
|
||||
message: 'hello'
|
||||
});
|
||||
|
||||
expect(key).toBe('Online:2026-03-01T00:00:00.000Z:usr_123:wrld_abc:hello');
|
||||
expect(key).toBe(
|
||||
'Online:2026-03-01T00:00:00.000Z:usr_123:wrld_abc:hello'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,7 +122,9 @@ vi.mock('../../../services/confusables', () => ({
|
||||
|
||||
vi.mock('../../../shared/utils', () => ({
|
||||
localeIncludes: (source, query) =>
|
||||
String(source ?? '').toLowerCase().includes(String(query ?? '').toLowerCase())
|
||||
String(source ?? '')
|
||||
.toLowerCase()
|
||||
.includes(String(query ?? '').toLowerCase())
|
||||
}));
|
||||
|
||||
vi.mock('../../../composables/useDataTableScrollHeight', () => ({
|
||||
@@ -141,7 +143,8 @@ vi.mock('../../../lib/table/useVrcxVueTable', () => ({
|
||||
getColumn: (id) =>
|
||||
id === 'bulkSelect'
|
||||
? {
|
||||
toggleVisibility: (...args) => mocks.toggleBulkColumnVisibility(...args)
|
||||
toggleVisibility: (...args) =>
|
||||
mocks.toggleBulkColumnVisibility(...args)
|
||||
}
|
||||
: null
|
||||
},
|
||||
@@ -260,7 +263,9 @@ function makeFriendCtx({ id, displayName, memo = '', dateJoined = null }) {
|
||||
}
|
||||
|
||||
function clickButtonByText(wrapper, text) {
|
||||
const button = wrapper.findAll('button').find((node) => node.text().trim() === text);
|
||||
const button = wrapper
|
||||
.findAll('button')
|
||||
.find((node) => node.text().trim() === text);
|
||||
if (!button) {
|
||||
throw new Error(`Cannot find button with text: ${text}`);
|
||||
}
|
||||
@@ -312,7 +317,9 @@ describe('FriendList.vue', () => {
|
||||
wrapper.vm.friendsListSearchChange();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.friendsListDisplayData.map((item) => item.id)).toEqual(['usr_1']);
|
||||
expect(
|
||||
wrapper.vm.friendsListDisplayData.map((item) => item.id)
|
||||
).toEqual(['usr_1']);
|
||||
expect(mocks.getAllUserStats).toHaveBeenCalled();
|
||||
expect(mocks.getAllUserMutualCount).toHaveBeenCalled();
|
||||
});
|
||||
@@ -320,15 +327,32 @@ describe('FriendList.vue', () => {
|
||||
test('opens charts tab from toolbar button', async () => {
|
||||
const wrapper = mount(FriendList);
|
||||
|
||||
await clickButtonByText(wrapper, 'view.friend_list.load_mutual_friends');
|
||||
await clickButtonByText(
|
||||
wrapper,
|
||||
'view.friend_list.load_mutual_friends'
|
||||
);
|
||||
|
||||
expect(mocks.routerPush).toHaveBeenCalledWith({ name: 'charts' });
|
||||
});
|
||||
|
||||
test('loads missing user profiles and shows completion toast', async () => {
|
||||
mocks.friends.value = new Map([
|
||||
['usr_1', makeFriendCtx({ id: 'usr_1', displayName: 'Alice', dateJoined: null })],
|
||||
['usr_2', makeFriendCtx({ id: 'usr_2', displayName: 'Bob', dateJoined: '2020-01-01' })]
|
||||
[
|
||||
'usr_1',
|
||||
makeFriendCtx({
|
||||
id: 'usr_1',
|
||||
displayName: 'Alice',
|
||||
dateJoined: null
|
||||
})
|
||||
],
|
||||
[
|
||||
'usr_2',
|
||||
makeFriendCtx({
|
||||
id: 'usr_2',
|
||||
displayName: 'Bob',
|
||||
dateJoined: '2020-01-01'
|
||||
})
|
||||
]
|
||||
]);
|
||||
const wrapper = mount(FriendList);
|
||||
await flushAsync();
|
||||
@@ -338,7 +362,9 @@ describe('FriendList.vue', () => {
|
||||
|
||||
expect(mocks.userGetUser).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.userGetUser).toHaveBeenCalledWith({ userId: 'usr_1' });
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith('view.friend_list.load_complete');
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith(
|
||||
'view.friend_list.load_complete'
|
||||
);
|
||||
});
|
||||
|
||||
test('select row emits lookup-user for id-less value and opens user dialog for id', () => {
|
||||
@@ -347,7 +373,9 @@ describe('FriendList.vue', () => {
|
||||
wrapper.vm.selectFriendsListRow({ displayName: 'Unknown' });
|
||||
wrapper.vm.selectFriendsListRow({ id: 'usr_99', displayName: 'Known' });
|
||||
|
||||
expect(wrapper.emitted('lookup-user')?.[0]?.[0]).toEqual({ displayName: 'Unknown' });
|
||||
expect(wrapper.emitted('lookup-user')?.[0]?.[0]).toEqual({
|
||||
displayName: 'Unknown'
|
||||
});
|
||||
expect(mocks.showUserDialog).toHaveBeenCalledWith('usr_99');
|
||||
});
|
||||
|
||||
|
||||
@@ -54,14 +54,7 @@
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '../../components/ui/select';
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select';
|
||||
import { useAppearanceSettingsStore, useFriendStore, useModalStore, useVrcxStore } from '../../stores';
|
||||
import { DataTableLayout } from '../../components/ui/data-table';
|
||||
import { InputGroupField } from '../../components/ui/input-group';
|
||||
@@ -91,9 +84,7 @@
|
||||
const typeFilter = friendLogTable.value.filters?.[0]?.value ?? [];
|
||||
const searchFilter = friendLogTable.value.filters?.[1]?.value ?? '';
|
||||
const hideUnfriendsFilter = friendLogTable.value.filters?.[2]?.value;
|
||||
const typeSet = Array.isArray(typeFilter)
|
||||
? new Set(typeFilter.map((value) => String(value).toLowerCase()))
|
||||
: null;
|
||||
const typeSet = Array.isArray(typeFilter) ? new Set(typeFilter.map((value) => String(value).toLowerCase())) : null;
|
||||
const searchValue = String(searchFilter).trim().toLowerCase();
|
||||
|
||||
const filtered = data.filter((row) => {
|
||||
|
||||
@@ -33,10 +33,9 @@ vi.mock('pinia', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores', () => ({
|
||||
@@ -104,7 +103,8 @@ vi.mock('../../../components/ui/input-group', () => ({
|
||||
InputGroupField: {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
template: '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />'
|
||||
template:
|
||||
'<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -116,7 +116,8 @@ vi.mock('../../../services/config', () => ({
|
||||
|
||||
vi.mock('../../../services/database', () => ({
|
||||
database: {
|
||||
deleteFriendLogHistory: (...args) => mocks.deleteFriendLogHistory(...args)
|
||||
deleteFriendLogHistory: (...args) =>
|
||||
mocks.deleteFriendLogHistory(...args)
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -171,7 +172,11 @@ describe('FriendLog.vue', () => {
|
||||
created_at: '2026-03-10T00:00:00.000Z'
|
||||
}
|
||||
];
|
||||
mocks.friendLogTable.value.filters = [{ value: ['Friend'] }, { value: 'ali' }, { value: true }];
|
||||
mocks.friendLogTable.value.filters = [
|
||||
{ value: ['Friend'] },
|
||||
{ value: 'ali' },
|
||||
{ value: true }
|
||||
];
|
||||
const wrapper = mount(FriendLog);
|
||||
|
||||
expect(wrapper.vm.friendLogDisplayData).toEqual([
|
||||
@@ -203,7 +208,10 @@ describe('FriendLog.vue', () => {
|
||||
|
||||
wrapper.vm.deleteFriendLog(row);
|
||||
|
||||
expect(mocks.removeFromArray).toHaveBeenCalledWith(mocks.friendLogTable.value.data, row);
|
||||
expect(mocks.removeFromArray).toHaveBeenCalledWith(
|
||||
mocks.friendLogTable.value.data,
|
||||
row
|
||||
);
|
||||
expect(mocks.deleteFriendLogHistory).toHaveBeenCalledWith(55);
|
||||
});
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ vi.mock('../../../stores', () => ({
|
||||
}),
|
||||
useFavoriteStore: () => ({
|
||||
favoriteFriendGroups: mocks.favoriteFriendGroups,
|
||||
groupedByGroupKeyFavoriteFriends: mocks.groupedByGroupKeyFavoriteFriends,
|
||||
groupedByGroupKeyFavoriteFriends:
|
||||
mocks.groupedByGroupKeyFavoriteFriends,
|
||||
localFriendFavorites: mocks.localFriendFavorites
|
||||
}),
|
||||
useLocationStore: () => ({
|
||||
@@ -193,7 +194,8 @@ vi.mock('../../../components/ui/switch', () => ({
|
||||
vi.mock('../components/FriendsLocationsCard.vue', () => ({
|
||||
default: {
|
||||
props: ['friend'],
|
||||
template: '<div data-testid="friend-card">{{ friend.displayName }}</div>'
|
||||
template:
|
||||
'<div data-testid="friend-card">{{ friend.displayName }}</div>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -244,12 +246,17 @@ describe('FriendsLocations.vue', () => {
|
||||
mocks.configSetBool.mockReset();
|
||||
mocks.virtualMeasure.mockReset();
|
||||
|
||||
mocks.configGetString.mockImplementation((_key, defaultValue) => Promise.resolve(defaultValue ?? '1'));
|
||||
mocks.configGetString.mockImplementation((_key, defaultValue) =>
|
||||
Promise.resolve(defaultValue ?? '1')
|
||||
);
|
||||
mocks.configGetBool.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
test('renders online friend cards after initial settings load', async () => {
|
||||
mocks.onlineFriends.value = [makeFriend('usr_1', 'Alice'), makeFriend('usr_2', 'Bob')];
|
||||
mocks.onlineFriends.value = [
|
||||
makeFriend('usr_1', 'Alice'),
|
||||
makeFriend('usr_2', 'Bob')
|
||||
];
|
||||
const wrapper = mount(FriendsLocations);
|
||||
await flushSettings();
|
||||
|
||||
@@ -258,11 +265,16 @@ describe('FriendsLocations.vue', () => {
|
||||
});
|
||||
|
||||
test('filters cards by search text in DOM', async () => {
|
||||
mocks.onlineFriends.value = [makeFriend('usr_1', 'Alice'), makeFriend('usr_2', 'Bob')];
|
||||
mocks.onlineFriends.value = [
|
||||
makeFriend('usr_1', 'Alice'),
|
||||
makeFriend('usr_2', 'Bob')
|
||||
];
|
||||
const wrapper = mount(FriendsLocations);
|
||||
await flushSettings();
|
||||
|
||||
await wrapper.get('[data-testid="friend-locations-search"]').setValue('bob');
|
||||
await wrapper
|
||||
.get('[data-testid="friend-locations-search"]')
|
||||
.setValue('bob');
|
||||
await flushSettings();
|
||||
|
||||
const cards = wrapper.findAll('[data-testid="friend-card"]');
|
||||
@@ -287,16 +299,26 @@ describe('FriendsLocations.vue', () => {
|
||||
await flushSettings();
|
||||
|
||||
await wrapper.get('[data-testid="set-scale"]').trigger('click');
|
||||
await wrapper.get('[data-testid="toggle-same-instance"]').trigger('click');
|
||||
await wrapper
|
||||
.get('[data-testid="toggle-same-instance"]')
|
||||
.trigger('click');
|
||||
|
||||
expect(mocks.configSetString).toHaveBeenCalledWith('VRCX_FriendLocationCardScale', '0.8');
|
||||
expect(mocks.configSetBool).toHaveBeenCalledWith('VRCX_FriendLocationShowSameInstance', true);
|
||||
expect(mocks.configSetString).toHaveBeenCalledWith(
|
||||
'VRCX_FriendLocationCardScale',
|
||||
'0.8'
|
||||
);
|
||||
expect(mocks.configSetBool).toHaveBeenCalledWith(
|
||||
'VRCX_FriendLocationShowSameInstance',
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('renders empty state when no rows match', async () => {
|
||||
const wrapper = mount(FriendsLocations);
|
||||
await flushSettings();
|
||||
|
||||
expect(wrapper.get('[data-testid="empty-state"]').text()).toBe('nomatch');
|
||||
expect(wrapper.get('[data-testid="empty-state"]').text()).toBe(
|
||||
'nomatch'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -487,10 +487,18 @@ describe('FriendsLocationsCard.vue', () => {
|
||||
ref: { location: 'wrld_12345:67890~region(us)' }
|
||||
})
|
||||
});
|
||||
const launchInviteItem = getMenuItemByText(wrapper, 'Launch/Invite');
|
||||
const inviteYourselfItem = getMenuItemByText(wrapper, 'Invite Yourself');
|
||||
const launchInviteItem = getMenuItemByText(
|
||||
wrapper,
|
||||
'Launch/Invite'
|
||||
);
|
||||
const inviteYourselfItem = getMenuItemByText(
|
||||
wrapper,
|
||||
'Invite Yourself'
|
||||
);
|
||||
expect(launchInviteItem?.attributes('data-disabled')).toBe('true');
|
||||
expect(inviteYourselfItem?.attributes('data-disabled')).toBe('true');
|
||||
expect(inviteYourselfItem?.attributes('data-disabled')).toBe(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -523,7 +531,10 @@ describe('FriendsLocationsCard.vue', () => {
|
||||
const wrapper = mountCard({
|
||||
friend: makeFriend({ state: 'online' })
|
||||
});
|
||||
const requestInviteItem = getMenuItemByText(wrapper, 'Request Invite');
|
||||
const requestInviteItem = getMenuItemByText(
|
||||
wrapper,
|
||||
'Request Invite'
|
||||
);
|
||||
await requestInviteItem.trigger('click');
|
||||
expect(mockSendRequestInvite).toHaveBeenCalledWith(
|
||||
{ platform: 'standalonewindows' },
|
||||
@@ -560,7 +571,10 @@ describe('FriendsLocationsCard.vue', () => {
|
||||
ref: { location: 'wrld_12345:67890~region(us)' }
|
||||
})
|
||||
});
|
||||
const selfInviteItem = getMenuItemByText(wrapper, 'Invite Yourself');
|
||||
const selfInviteItem = getMenuItemByText(
|
||||
wrapper,
|
||||
'Invite Yourself'
|
||||
);
|
||||
await selfInviteItem.trigger('click');
|
||||
expect(mockSelfInvite).toHaveBeenCalledWith({
|
||||
instanceId: '67890~region(us)',
|
||||
@@ -583,9 +597,9 @@ describe('FriendsLocationsCard.vue', () => {
|
||||
active: false
|
||||
});
|
||||
const wrapper = mountCard();
|
||||
expect(wrapper.find('.friend-card__status-dot').classes()).toContain(
|
||||
'friend-card__status-dot--join'
|
||||
);
|
||||
expect(
|
||||
wrapper.find('.friend-card__status-dot').classes()
|
||||
).toContain('friend-card__status-dot--join');
|
||||
});
|
||||
|
||||
test('shows active busy status class when active + busy', () => {
|
||||
@@ -597,9 +611,9 @@ describe('FriendsLocationsCard.vue', () => {
|
||||
const wrapper = mountCard({
|
||||
friend: makeFriend({ status: 'busy' })
|
||||
});
|
||||
expect(wrapper.find('.friend-card__status-dot').classes()).toContain(
|
||||
'friend-card__status-dot--active-busy'
|
||||
);
|
||||
expect(
|
||||
wrapper.find('.friend-card__status-dot').classes()
|
||||
).toContain('friend-card__status-dot--active-busy');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,25 +2,61 @@ 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: '' } } }));
|
||||
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 }),
|
||||
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('../../../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('../../../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('../../../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';
|
||||
|
||||
@@ -62,14 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Field, FieldContent, FieldGroup, FieldLabel } from '@/components/ui/field';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -18,10 +18,9 @@ vi.mock('pinia', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
@@ -105,7 +104,9 @@ function mountDialog() {
|
||||
}
|
||||
|
||||
function clickButtonByText(wrapper, text) {
|
||||
const button = wrapper.findAll('button').find((node) => node.text().trim() === text);
|
||||
const button = wrapper
|
||||
.findAll('button')
|
||||
.find((node) => node.text().trim() === text);
|
||||
if (!button) {
|
||||
throw new Error(`Cannot find button with text: ${text}`);
|
||||
}
|
||||
@@ -130,7 +131,8 @@ describe('LoginSettingsDialog.vue', () => {
|
||||
mocks.authStore = {
|
||||
loginForm: mocks.loginForm,
|
||||
enableCustomEndpoint: mocks.enableCustomEndpoint,
|
||||
toggleCustomEndpoint: (...args) => mocks.toggleCustomEndpoint(...args)
|
||||
toggleCustomEndpoint: (...args) =>
|
||||
mocks.toggleCustomEndpoint(...args)
|
||||
};
|
||||
|
||||
globalThis.VRCXStorage = {
|
||||
@@ -145,13 +147,17 @@ describe('LoginSettingsDialog.vue', () => {
|
||||
await wrapper.get('[data-testid="open-dialog"]').trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.get('#login-settings-proxy').element.value).toBe('http://proxy.local:7890');
|
||||
expect(wrapper.get('#login-settings-proxy').element.value).toBe(
|
||||
'http://proxy.local:7890'
|
||||
);
|
||||
});
|
||||
|
||||
test('toggles custom endpoint and shows endpoint inputs', async () => {
|
||||
const wrapper = mountDialog();
|
||||
|
||||
await wrapper.get('[data-testid="toggle-custom-endpoint"]').trigger('click');
|
||||
await wrapper
|
||||
.get('[data-testid="toggle-custom-endpoint"]')
|
||||
.trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(mocks.toggleCustomEndpoint).toHaveBeenCalledTimes(1);
|
||||
@@ -161,10 +167,14 @@ describe('LoginSettingsDialog.vue', () => {
|
||||
const wrapper = mountDialog();
|
||||
|
||||
await wrapper.get('[data-testid="open-dialog"]').trigger('click');
|
||||
await wrapper.get('#login-settings-proxy').setValue('http://127.0.0.1:8080');
|
||||
await wrapper
|
||||
.get('#login-settings-proxy')
|
||||
.setValue('http://127.0.0.1:8080');
|
||||
await clickButtonByText(wrapper, 'prompt.proxy_settings.close');
|
||||
|
||||
expect(mocks.setProxyServer).toHaveBeenCalledWith('http://127.0.0.1:8080');
|
||||
expect(mocks.setProxyServer).toHaveBeenCalledWith(
|
||||
'http://127.0.0.1:8080'
|
||||
);
|
||||
expect(globalThis.VRCXStorage.Set).toHaveBeenCalledWith(
|
||||
'VRCX_ProxyServer',
|
||||
'http://127.0.0.1:8080'
|
||||
@@ -177,10 +187,14 @@ describe('LoginSettingsDialog.vue', () => {
|
||||
const wrapper = mountDialog();
|
||||
|
||||
await wrapper.get('[data-testid="open-dialog"]').trigger('click');
|
||||
await wrapper.get('#login-settings-proxy').setValue('http://192.168.0.2:3128');
|
||||
await wrapper
|
||||
.get('#login-settings-proxy')
|
||||
.setValue('http://192.168.0.2:3128');
|
||||
await clickButtonByText(wrapper, 'prompt.proxy_settings.restart');
|
||||
|
||||
expect(mocks.setProxyServer).toHaveBeenCalledWith('http://192.168.0.2:3128');
|
||||
expect(mocks.setProxyServer).toHaveBeenCalledWith(
|
||||
'http://192.168.0.2:3128'
|
||||
);
|
||||
expect(globalThis.VRCXStorage.Save).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.restartVRCX).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
@@ -124,9 +124,7 @@
|
||||
const data = playerModerationTable.value.data;
|
||||
const typeFilter = playerModerationTable.value.filters?.[0]?.value ?? [];
|
||||
const searchFilter = playerModerationTable.value.filters?.[1]?.value ?? '';
|
||||
const typeSet = Array.isArray(typeFilter)
|
||||
? new Set(typeFilter.map((value) => String(value).toLowerCase()))
|
||||
: null;
|
||||
const typeSet = Array.isArray(typeFilter) ? new Set(typeFilter.map((value) => String(value).toLowerCase())) : null;
|
||||
const searchValue = String(searchFilter).trim().toLowerCase();
|
||||
|
||||
return data.filter((row) => {
|
||||
|
||||
@@ -35,17 +35,18 @@ vi.mock('pinia', async (importOriginal) => {
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores', () => ({
|
||||
useModerationStore: () => ({
|
||||
playerModerationTable: mocks.playerModerationTable,
|
||||
refreshPlayerModerations: (...args) => mocks.refreshPlayerModerations(...args),
|
||||
handlePlayerModerationDelete: (...args) => mocks.handlePlayerModerationDelete(...args)
|
||||
refreshPlayerModerations: (...args) =>
|
||||
mocks.refreshPlayerModerations(...args),
|
||||
handlePlayerModerationDelete: (...args) =>
|
||||
mocks.handlePlayerModerationDelete(...args)
|
||||
}),
|
||||
useAppearanceSettingsStore: () => ({
|
||||
tablePageSizes: [10, 25, 50],
|
||||
@@ -68,12 +69,14 @@ vi.mock('../../../services/config.js', () => ({
|
||||
|
||||
vi.mock('../../../api', () => ({
|
||||
playerModerationRequest: {
|
||||
deletePlayerModeration: (...args) => mocks.deletePlayerModeration(...args)
|
||||
deletePlayerModeration: (...args) =>
|
||||
mocks.deletePlayerModeration(...args)
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../../coordinators/moderationCoordinator', () => ({
|
||||
runRefreshPlayerModerationsFlow: (...args) => mocks.refreshPlayerModerations(...args)
|
||||
runRefreshPlayerModerationsFlow: (...args) =>
|
||||
mocks.refreshPlayerModerations(...args)
|
||||
}));
|
||||
|
||||
vi.mock('../../../shared/constants', async (importOriginal) => {
|
||||
@@ -120,7 +123,8 @@ vi.mock('@/components/ui/select', () => ({
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="moderation-button" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="moderation-button" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -189,15 +193,23 @@ describe('Moderation.vue', () => {
|
||||
|
||||
mocks.configGetString.mockResolvedValue('["block"]');
|
||||
mocks.modalConfirm.mockResolvedValue({ ok: true });
|
||||
mocks.deletePlayerModeration.mockResolvedValue({ ok: true, id: 'pm_1' });
|
||||
mocks.deletePlayerModeration.mockResolvedValue({
|
||||
ok: true,
|
||||
id: 'pm_1'
|
||||
});
|
||||
});
|
||||
|
||||
test('loads persisted moderation filter on init', async () => {
|
||||
mountModeration();
|
||||
await flushAsync();
|
||||
|
||||
expect(mocks.configGetString).toHaveBeenCalledWith('VRCX_playerModerationTableFilters', '[]');
|
||||
expect(mocks.playerModerationTable.value.filters[0].value).toEqual(['block']);
|
||||
expect(mocks.configGetString).toHaveBeenCalledWith(
|
||||
'VRCX_playerModerationTableFilters',
|
||||
'[]'
|
||||
);
|
||||
expect(mocks.playerModerationTable.value.filters[0].value).toEqual([
|
||||
'block'
|
||||
]);
|
||||
});
|
||||
|
||||
test('updates moderation filter and persists value', async () => {
|
||||
@@ -207,7 +219,9 @@ describe('Moderation.vue', () => {
|
||||
wrapper.vm.handleModerationFilterChange(['mute']);
|
||||
await nextTick();
|
||||
|
||||
expect(mocks.playerModerationTable.value.filters[0].value).toEqual(['mute']);
|
||||
expect(mocks.playerModerationTable.value.filters[0].value).toEqual([
|
||||
'mute'
|
||||
]);
|
||||
expect(mocks.configSetString).toHaveBeenCalledWith(
|
||||
'VRCX_playerModerationTableFilters',
|
||||
JSON.stringify(['mute'])
|
||||
@@ -216,11 +230,22 @@ describe('Moderation.vue', () => {
|
||||
|
||||
test('filters table rows by type and search text', async () => {
|
||||
mocks.playerModerationTable.value.data = [
|
||||
{ type: 'block', sourceDisplayName: 'Alpha', targetDisplayName: 'Beta' },
|
||||
{ type: 'mute', sourceDisplayName: 'Gamma', targetDisplayName: 'Delta' },
|
||||
{
|
||||
type: 'block',
|
||||
sourceDisplayName: 'Alpha',
|
||||
targetDisplayName: 'Beta'
|
||||
},
|
||||
{
|
||||
type: 'mute',
|
||||
sourceDisplayName: 'Gamma',
|
||||
targetDisplayName: 'Delta'
|
||||
},
|
||||
{ type: 'mute', sourceDisplayName: 'X', targetDisplayName: 'Alice' }
|
||||
];
|
||||
mocks.playerModerationTable.value.filters = [{ value: ['mute'] }, { value: 'ali' }];
|
||||
mocks.playerModerationTable.value.filters = [
|
||||
{ value: ['mute'] },
|
||||
{ value: 'ali' }
|
||||
];
|
||||
|
||||
const wrapper = mountModeration();
|
||||
await flushAsync();
|
||||
@@ -246,7 +271,10 @@ describe('Moderation.vue', () => {
|
||||
moderated: 'usr_1',
|
||||
type: 'mute'
|
||||
});
|
||||
expect(mocks.handlePlayerModerationDelete).toHaveBeenCalledWith({ ok: true, id: 'pm_1' });
|
||||
expect(mocks.handlePlayerModerationDelete).toHaveBeenCalledWith({
|
||||
ok: true,
|
||||
id: 'pm_1'
|
||||
});
|
||||
});
|
||||
|
||||
test('delete prompt confirms before deletion', async () => {
|
||||
|
||||
@@ -89,9 +89,16 @@ describe('views/Moderation/columns.jsx', () => {
|
||||
});
|
||||
|
||||
test('source and target cells open corresponding user dialog', () => {
|
||||
const cols = createColumns({ onDelete: vi.fn(), onDeletePrompt: vi.fn() });
|
||||
const sourceCol = cols.find((c) => c.accessorKey === 'sourceDisplayName');
|
||||
const targetCol = cols.find((c) => c.accessorKey === 'targetDisplayName');
|
||||
const cols = createColumns({
|
||||
onDelete: vi.fn(),
|
||||
onDeletePrompt: vi.fn()
|
||||
});
|
||||
const sourceCol = cols.find(
|
||||
(c) => c.accessorKey === 'sourceDisplayName'
|
||||
);
|
||||
const targetCol = cols.find(
|
||||
(c) => c.accessorKey === 'targetDisplayName'
|
||||
);
|
||||
const row = {
|
||||
original: {
|
||||
sourceUserId: 'usr_source',
|
||||
@@ -104,15 +111,24 @@ describe('views/Moderation/columns.jsx', () => {
|
||||
|
||||
const sourceCell = sourceCol.cell({ row });
|
||||
const targetCell = targetCol.cell({ row });
|
||||
findNode(sourceCell, (n) => n.type === 'span' && typeof n.props?.onClick === 'function').props.onClick();
|
||||
findNode(targetCell, (n) => n.type === 'span' && typeof n.props?.onClick === 'function').props.onClick();
|
||||
findNode(
|
||||
sourceCell,
|
||||
(n) => n.type === 'span' && typeof n.props?.onClick === 'function'
|
||||
).props.onClick();
|
||||
findNode(
|
||||
targetCell,
|
||||
(n) => n.type === 'span' && typeof n.props?.onClick === 'function'
|
||||
).props.onClick();
|
||||
|
||||
expect(mocks.showUserDialog).toHaveBeenNthCalledWith(1, 'usr_source');
|
||||
expect(mocks.showUserDialog).toHaveBeenNthCalledWith(2, 'usr_target');
|
||||
});
|
||||
|
||||
test('action cell hidden when source user is not current user', () => {
|
||||
const cols = createColumns({ onDelete: vi.fn(), onDeletePrompt: vi.fn() });
|
||||
const cols = createColumns({
|
||||
onDelete: vi.fn(),
|
||||
onDeletePrompt: vi.fn()
|
||||
});
|
||||
const actionCol = cols.find((c) => c.id === 'action');
|
||||
const vnode = actionCol.cell({
|
||||
row: {
|
||||
|
||||
@@ -319,11 +319,7 @@
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '../../components/ui/dropdown-menu';
|
||||
import { Field, FieldContent, FieldLabel } from '../../components/ui/field';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
|
||||
import {
|
||||
applyAvatar,
|
||||
selectAvatarWithoutConfirmation,
|
||||
showAvatarDialog
|
||||
} from '../../coordinators/avatarCoordinator';
|
||||
import { applyAvatar, selectAvatarWithoutConfirmation, showAvatarDialog } from '../../coordinators/avatarCoordinator';
|
||||
import {
|
||||
handleImageUploadInput,
|
||||
resizeImageToFitLimits,
|
||||
@@ -459,8 +455,7 @@
|
||||
if (searchText.value) {
|
||||
const query = searchText.value.toLowerCase();
|
||||
list = list.filter(
|
||||
(a) =>
|
||||
a.name?.toLowerCase().includes(query) || a.$tags?.some((t) => t.tag.toLowerCase().includes(query))
|
||||
(a) => a.name?.toLowerCase().includes(query) || a.$tags?.some((t) => t.tag.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ vi.mock('@/components/ui/popover', () => ({
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="button" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="button" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -124,7 +125,9 @@ describe('ManageTagsDialog.vue', () => {
|
||||
|
||||
const cancelButton = wrapper
|
||||
.findAll('[data-testid="button"]')
|
||||
.find((node) => node.text().includes('prompt.rename_avatar.cancel'));
|
||||
.find((node) =>
|
||||
node.text().includes('prompt.rename_avatar.cancel')
|
||||
);
|
||||
|
||||
expect(cancelButton).toBeTruthy();
|
||||
await cancelButton.trigger('click');
|
||||
|
||||
@@ -3,7 +3,10 @@ import { nextTick, ref } from 'vue';
|
||||
import { flushPromises, mount } from '@vue/test-utils';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
currentUser: { value: { currentAvatar: 'avtr_current', $previousAvatarSwapTime: 0 }, __v_isRef: true },
|
||||
currentUser: {
|
||||
value: { currentAvatar: 'avtr_current', $previousAvatarSwapTime: 0 },
|
||||
__v_isRef: true
|
||||
},
|
||||
modalConfirm: vi.fn(),
|
||||
configGetString: vi.fn(),
|
||||
configSetString: vi.fn(),
|
||||
@@ -76,7 +79,8 @@ vi.mock('../../../stores', () => ({
|
||||
|
||||
vi.mock('../../../coordinators/avatarCoordinator', () => ({
|
||||
applyAvatar: (...args) => mocks.applyAvatar(...args),
|
||||
selectAvatarWithoutConfirmation: (...args) => mocks.selectAvatarWithoutConfirmation(...args),
|
||||
selectAvatarWithoutConfirmation: (...args) =>
|
||||
mocks.selectAvatarWithoutConfirmation(...args),
|
||||
showAvatarDialog: (...args) => mocks.showAvatarDialog(...args)
|
||||
}));
|
||||
|
||||
@@ -140,7 +144,10 @@ vi.mock('../composables/useAvatarCardGrid.js', () => ({
|
||||
gridStyle: ref(() => ({ '--avatar-grid-columns': '1' })),
|
||||
chunkIntoRows: (items, prefix = 'row') =>
|
||||
Array.isArray(items)
|
||||
? items.map((item, index) => ({ key: `${prefix}:${index}`, items: [item] }))
|
||||
? items.map((item, index) => ({
|
||||
key: `${prefix}:${index}`,
|
||||
items: [item]
|
||||
}))
|
||||
: [],
|
||||
estimateRowHeight: () => 80,
|
||||
updateContainerWidth: vi.fn()
|
||||
@@ -215,7 +222,8 @@ vi.mock('../../../components/ui/badge', () => ({
|
||||
vi.mock('../../../components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="button" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="button" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -248,7 +256,8 @@ vi.mock('../components/MyAvatarCard.vue', () => ({
|
||||
default: {
|
||||
props: ['avatar'],
|
||||
emits: ['click', 'context-action'],
|
||||
template: '<button data-testid="avatar-card" @click="$emit(\'click\')">{{ avatar.name }}</button>'
|
||||
template:
|
||||
'<button data-testid="avatar-card" @click="$emit(\'click\')">{{ avatar.name }}</button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -278,7 +287,10 @@ describe('MyAvatars.vue', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mocks.currentUser.value = { currentAvatar: 'avtr_current', $previousAvatarSwapTime: 0 };
|
||||
mocks.currentUser.value = {
|
||||
currentAvatar: 'avtr_current',
|
||||
$previousAvatarSwapTime: 0
|
||||
};
|
||||
mocks.modalConfirm.mockResolvedValue({ ok: true });
|
||||
mocks.configGetString.mockImplementation((key, defaultValue) => {
|
||||
if (key === 'VRCX_MyAvatarsViewMode') {
|
||||
@@ -286,7 +298,9 @@ describe('MyAvatars.vue', () => {
|
||||
}
|
||||
return Promise.resolve(defaultValue ?? '');
|
||||
});
|
||||
mocks.getAllAvatarTags.mockResolvedValue(new Map([['avtr_1', [{ tag: 'fun', color: null }]]]));
|
||||
mocks.getAllAvatarTags.mockResolvedValue(
|
||||
new Map([['avtr_1', [{ tag: 'fun', color: null }]]])
|
||||
);
|
||||
mocks.getAvatarTimeSpent.mockResolvedValue({ timeSpent: 1000 });
|
||||
mocks.processBulk.mockImplementation(async ({ handle, done }) => {
|
||||
handle({
|
||||
@@ -316,7 +330,9 @@ describe('MyAvatars.vue', () => {
|
||||
const wrapper = mount(MyAvatars);
|
||||
await flushAll();
|
||||
|
||||
expect(wrapper.find('[data-testid="table-layout"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="table-layout"]').exists()).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('persists view mode when toggled', async () => {
|
||||
@@ -325,7 +341,10 @@ describe('MyAvatars.vue', () => {
|
||||
|
||||
await wrapper.get('[data-testid="set-table"]').trigger('click');
|
||||
|
||||
expect(mocks.configSetString).toHaveBeenCalledWith('VRCX_MyAvatarsViewMode', 'table');
|
||||
expect(mocks.configSetString).toHaveBeenCalledWith(
|
||||
'VRCX_MyAvatarsViewMode',
|
||||
'table'
|
||||
);
|
||||
});
|
||||
|
||||
test('confirms and selects avatar when grid card is clicked', async () => {
|
||||
@@ -336,6 +355,8 @@ describe('MyAvatars.vue', () => {
|
||||
await flushAll();
|
||||
|
||||
expect(mocks.modalConfirm).toHaveBeenCalled();
|
||||
expect(mocks.selectAvatarWithoutConfirmation).toHaveBeenCalledWith('avtr_1');
|
||||
expect(mocks.selectAvatarWithoutConfirmation).toHaveBeenCalledWith(
|
||||
'avtr_1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,7 +56,8 @@ vi.mock('@/components/ui/badge', () => ({
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="button" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="button" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -127,7 +128,9 @@ describe('MyAvatarCard.vue', () => {
|
||||
const wrapper = mountCard();
|
||||
const detailsItem = wrapper
|
||||
.findAll('[data-testid="ctx-item"]')
|
||||
.find((node) => node.text().includes('dialog.avatar.actions.view_details'));
|
||||
.find((node) =>
|
||||
node.text().includes('dialog.avatar.actions.view_details')
|
||||
);
|
||||
|
||||
expect(detailsItem).toBeTruthy();
|
||||
await detailsItem.trigger('click');
|
||||
@@ -143,7 +146,9 @@ describe('MyAvatarCard.vue', () => {
|
||||
const wrapper = mountCard({ currentAvatarId: 'avtr_1' });
|
||||
const wearItem = wrapper
|
||||
.findAll('[data-testid="ctx-item"]')
|
||||
.find((node) => node.text().includes('view.favorite.select_avatar_tooltip'));
|
||||
.find((node) =>
|
||||
node.text().includes('view.favorite.select_avatar_tooltip')
|
||||
);
|
||||
|
||||
expect(wearItem.attributes('disabled')).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -31,10 +31,9 @@ vi.mock('pinia', async (importOriginal) => {
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores', () => ({
|
||||
@@ -44,7 +43,8 @@ vi.mock('../../../stores', () => ({
|
||||
usePhotonStore: () => ({
|
||||
photonLoggingEnabled: mocks.photonLoggingEnabled,
|
||||
chatboxUserBlacklist: mocks.chatboxUserBlacklist,
|
||||
saveChatboxUserBlacklist: (...args) => mocks.saveChatboxUserBlacklist(...args)
|
||||
saveChatboxUserBlacklist: (...args) =>
|
||||
mocks.saveChatboxUserBlacklist(...args)
|
||||
}),
|
||||
useUserStore: () => ({
|
||||
currentUser: mocks.currentUser
|
||||
@@ -59,10 +59,12 @@ vi.mock('../../../stores', () => ({
|
||||
currentInstanceLocation: mocks.currentInstanceLocation,
|
||||
currentInstanceWorld: mocks.currentInstanceWorld,
|
||||
currentInstanceUsersData: mocks.currentInstanceUsersData,
|
||||
getCurrentInstanceUserList: (...args) => mocks.getCurrentInstanceUserList(...args)
|
||||
getCurrentInstanceUserList: (...args) =>
|
||||
mocks.getCurrentInstanceUserList(...args)
|
||||
}),
|
||||
useGalleryStore: () => ({
|
||||
showFullscreenImageDialog: (...args) => mocks.showFullscreenImageDialog(...args)
|
||||
showFullscreenImageDialog: (...args) =>
|
||||
mocks.showFullscreenImageDialog(...args)
|
||||
})
|
||||
}));
|
||||
|
||||
@@ -78,7 +80,8 @@ vi.mock('../../../lib/table/useVrcxVueTable', () => ({
|
||||
getColumn: (id) =>
|
||||
id === 'photonId'
|
||||
? {
|
||||
toggleVisibility: (...args) => mocks.photonColumnToggleVisibility(...args)
|
||||
toggleVisibility: (...args) =>
|
||||
mocks.photonColumnToggleVisibility(...args)
|
||||
}
|
||||
: null,
|
||||
getRowModel: () => ({ rows: mocks.currentInstanceUsersData.value })
|
||||
@@ -152,7 +155,9 @@ describe('PlayerList.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.randomUserColours = ref(false);
|
||||
mocks.photonLoggingEnabled = ref(false);
|
||||
mocks.chatboxUserBlacklist = ref(new Map([['usr_blocked', 'Blocked User']]));
|
||||
mocks.chatboxUserBlacklist = ref(
|
||||
new Map([['usr_blocked', 'Blocked User']])
|
||||
);
|
||||
mocks.lastLocation = ref({
|
||||
playerList: new Set(),
|
||||
friendList: new Set(),
|
||||
@@ -219,7 +224,9 @@ describe('PlayerList.vue', () => {
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="row-click-with-id"]').trigger('click');
|
||||
await wrapper.get('[data-testid="row-click-without-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' });
|
||||
@@ -252,11 +259,19 @@ describe('PlayerList.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.get('[data-testid="chatbox-dialog"]').attributes('data-visible')).toBe('false');
|
||||
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');
|
||||
expect(
|
||||
wrapper
|
||||
.get('[data-testid="chatbox-dialog"]')
|
||||
.attributes('data-visible')
|
||||
).toBe('true');
|
||||
});
|
||||
|
||||
test('deletes chatbox blacklist user and refreshes list', async () => {
|
||||
@@ -269,7 +284,9 @@ describe('PlayerList.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="emit-delete-chatbox"]').trigger('click');
|
||||
await wrapper
|
||||
.get('[data-testid="emit-delete-chatbox"]')
|
||||
.trigger('click');
|
||||
|
||||
expect(mocks.chatboxUserBlacklist.value.has('usr_blocked')).toBe(false);
|
||||
expect(mocks.saveChatboxUserBlacklist).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -27,7 +27,9 @@ vi.mock('../../../shared/utils', () => ({
|
||||
|
||||
vi.mock('../../../components/Timer.vue', () => ({ default: 'Timer' }));
|
||||
vi.mock('../../../components/ui/button', () => ({ Button: 'Button' }));
|
||||
vi.mock('../../../components/ui/tooltip', () => ({ TooltipWrapper: 'TooltipWrapper' }));
|
||||
vi.mock('../../../components/ui/tooltip', () => ({
|
||||
TooltipWrapper: 'TooltipWrapper'
|
||||
}));
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
ArrowUpDown: 'ArrowUpDown',
|
||||
Monitor: 'Monitor',
|
||||
@@ -131,10 +133,14 @@ describe('views/PlayerList/columns.jsx', () => {
|
||||
});
|
||||
const photonCol = cols.find((c) => c.id === 'photonId');
|
||||
const blockCell = photonCol.cell({ row });
|
||||
findNode(blockCell, (n) => n.type === 'button').props.onClick({ stopPropagation: vi.fn() });
|
||||
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 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 },
|
||||
@@ -144,7 +150,9 @@ describe('views/PlayerList/columns.jsx', () => {
|
||||
});
|
||||
const photonBlocked = colsBlocked.find((c) => c.id === 'photonId');
|
||||
const unblockCell = photonBlocked.cell({ row });
|
||||
findNode(unblockCell, (n) => n.type === 'button').props.onClick({ stopPropagation: vi.fn() });
|
||||
findNode(unblockCell, (n) => n.type === 'button').props.onClick({
|
||||
stopPropagation: vi.fn()
|
||||
});
|
||||
expect(mocks.onUnblockChatbox).toHaveBeenCalledWith('usr_1');
|
||||
});
|
||||
|
||||
@@ -180,8 +188,12 @@ describe('views/PlayerList/columns.jsx', () => {
|
||||
});
|
||||
const bioLinkCol = cols.find((c) => c.id === 'bioLink');
|
||||
const cell = bioLinkCol.cell({ row });
|
||||
findNode(cell, (n) => n.type === 'img').props.onClick({ stopPropagation: vi.fn() });
|
||||
findNode(cell, (n) => n.type === 'img').props.onClick({
|
||||
stopPropagation: vi.fn()
|
||||
});
|
||||
|
||||
expect(mocks.openExternalLink).toHaveBeenCalledWith('https://example.com');
|
||||
expect(mocks.openExternalLink).toHaveBeenCalledWith(
|
||||
'https://example.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,8 +135,7 @@
|
||||
if (!query) return true;
|
||||
|
||||
return (
|
||||
localeIncludes(row?.displayName ?? '', query, comparer) ||
|
||||
localeIncludes(row?.text ?? '', query, comparer)
|
||||
localeIncludes(row?.displayName ?? '', query, comparer) || localeIncludes(row?.text ?? '', query, comparer)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -126,7 +126,9 @@
|
||||
<Spinner class="text-2xl" />
|
||||
</div>
|
||||
<template v-else-if="searchWorldResults.length > 0">
|
||||
<ItemGroup class="grid gap-3" style="grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))">
|
||||
<ItemGroup
|
||||
class="grid gap-3"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))">
|
||||
<Item
|
||||
v-for="world in searchWorldResults"
|
||||
:key="world.id"
|
||||
@@ -200,7 +202,9 @@
|
||||
<Spinner class="text-2xl" />
|
||||
</div>
|
||||
<template v-else-if="searchAvatarPage.length > 0">
|
||||
<ItemGroup class="grid gap-3" style="grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))">
|
||||
<ItemGroup
|
||||
class="grid gap-3"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))">
|
||||
<Item
|
||||
v-for="avatar in searchAvatarPage"
|
||||
:key="avatar.id"
|
||||
@@ -300,15 +304,7 @@
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import AvatarProviderDialog from '../Settings/dialogs/AvatarProviderDialog.vue';
|
||||
import SearchPagination from './components/SearchPagination.vue';
|
||||
import {
|
||||
Item,
|
||||
ItemContent,
|
||||
ItemDescription,
|
||||
ItemGroup,
|
||||
ItemHeader,
|
||||
ItemMedia,
|
||||
ItemTitle
|
||||
} from '@/components/ui/item';
|
||||
import { Item, ItemContent, ItemDescription, ItemGroup, ItemHeader, ItemMedia, ItemTitle } from '@/components/ui/item';
|
||||
|
||||
import { computed, onUnmounted, ref } from 'vue';
|
||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||
@@ -408,14 +404,8 @@
|
||||
clearWorldSearch
|
||||
} = useSearchWorld();
|
||||
|
||||
const {
|
||||
searchGroupParams,
|
||||
searchGroupResults,
|
||||
isSearchGroupLoading,
|
||||
searchGroup,
|
||||
moreSearchGroup,
|
||||
clearGroupSearch
|
||||
} = useSearchGroup();
|
||||
const { searchGroupParams, searchGroupResults, isSearchGroupLoading, searchGroup, moreSearchGroup, clearGroupSearch } =
|
||||
useSearchGroup();
|
||||
|
||||
const paginationConfig = computed(() => {
|
||||
switch (activeSearchTab.value) {
|
||||
|
||||
@@ -102,7 +102,8 @@ vi.mock('../../../stores', () => ({
|
||||
randomUserColours: mocks.randomUserColours
|
||||
}),
|
||||
useAvatarProviderStore: () => ({
|
||||
avatarRemoteDatabaseProviderList: mocks.avatarRemoteDatabaseProviderList,
|
||||
avatarRemoteDatabaseProviderList:
|
||||
mocks.avatarRemoteDatabaseProviderList,
|
||||
avatarRemoteDatabaseProvider: mocks.avatarRemoteDatabaseProvider,
|
||||
isAvatarProviderDialogVisible: mocks.isAvatarProviderDialogVisible,
|
||||
setAvatarProvider: (...args) => mocks.setAvatarProvider(...args)
|
||||
@@ -171,14 +172,22 @@ vi.mock('@/components/ui/tabs', () => ({
|
||||
'</div>'
|
||||
},
|
||||
TabsList: { template: '<div><slot /></div>' },
|
||||
TabsTrigger: { props: ['value'], template: '<button :data-value="value"><slot /></button>' },
|
||||
TabsContent: { props: ['value'], template: '<section :data-testid="`content-${value}`"><slot /></section>' }
|
||||
TabsTrigger: {
|
||||
props: ['value'],
|
||||
template: '<button :data-value="value"><slot /></button>'
|
||||
},
|
||||
TabsContent: {
|
||||
props: ['value'],
|
||||
template:
|
||||
'<section :data-testid="`content-${value}`"><slot /></section>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="button" v-bind="$attrs" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="button" v-bind="$attrs" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -212,7 +221,8 @@ vi.mock('@/components/ui/select', () => ({
|
||||
vi.mock('@/components/ui/item', () => ({
|
||||
Item: {
|
||||
emits: ['click'],
|
||||
template: '<article class="item" @click="$emit(\'click\')"><slot /></article>'
|
||||
template:
|
||||
'<article class="item" @click="$emit(\'click\')"><slot /></article>'
|
||||
},
|
||||
ItemGroup: { template: '<div><slot /></div>' },
|
||||
ItemHeader: { template: '<div><slot /></div>' },
|
||||
@@ -254,7 +264,9 @@ function mountSearch() {
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<div><slot /></div>' },
|
||||
AvatarProviderDialog: { template: '<div data-testid="avatar-provider-dialog" />' },
|
||||
AvatarProviderDialog: {
|
||||
template: '<div data-testid="avatar-provider-dialog" />'
|
||||
},
|
||||
SearchPagination: {
|
||||
props: ['show', 'prevDisabled', 'nextDisabled'],
|
||||
emits: ['prev', 'next'],
|
||||
@@ -310,16 +322,24 @@ describe('Search.vue', () => {
|
||||
await wrapper.get('button.ml-2').trigger('click');
|
||||
|
||||
expect(mocks.useSearchUserApi.clearUserSearch).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.useSearchWorldApi.clearWorldSearch).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.useSearchAvatarApi.clearAvatarSearch).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.useSearchGroupApi.clearGroupSearch).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.useSearchWorldApi.clearWorldSearch).toHaveBeenCalledTimes(
|
||||
1
|
||||
);
|
||||
expect(
|
||||
mocks.useSearchAvatarApi.clearAvatarSearch
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.useSearchGroupApi.clearGroupSearch).toHaveBeenCalledTimes(
|
||||
1
|
||||
);
|
||||
expect(mocks.clearSearch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('runs user search on Enter when active tab is user', async () => {
|
||||
const wrapper = mountSearch();
|
||||
|
||||
await wrapper.get('[data-testid="search-input"]').trigger('keyup.enter');
|
||||
await wrapper
|
||||
.get('[data-testid="search-input"]')
|
||||
.trigger('keyup.enter');
|
||||
|
||||
expect(mocks.useSearchUserApi.searchUser).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.useSearchAvatarApi.searchAvatar).not.toHaveBeenCalled();
|
||||
@@ -330,9 +350,13 @@ describe('Search.vue', () => {
|
||||
mocks.searchText.value = 'ab';
|
||||
|
||||
await wrapper.get('[data-testid="set-tab-avatar"]').trigger('click');
|
||||
await wrapper.get('[data-testid="search-input"]').trigger('keyup.enter');
|
||||
await wrapper
|
||||
.get('[data-testid="search-input"]')
|
||||
.trigger('keyup.enter');
|
||||
|
||||
expect(mocks.toastWarning).toHaveBeenCalledWith('view.search.avatar.min_chars_warning');
|
||||
expect(mocks.toastWarning).toHaveBeenCalledWith(
|
||||
'view.search.avatar.min_chars_warning'
|
||||
);
|
||||
expect(mocks.useSearchAvatarApi.searchAvatar).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -43,8 +43,14 @@ describe('useSearchAvatar', () => {
|
||||
await api.searchAvatar();
|
||||
|
||||
expect(mocks.lookupAvatars).toHaveBeenCalledWith('search', 'alice');
|
||||
expect(api.searchAvatarResults.value.map((x) => x.id)).toEqual(['avtr_1', 'avtr_2']);
|
||||
expect(api.searchAvatarPage.value.map((x) => x.id)).toEqual(['avtr_1', 'avtr_2']);
|
||||
expect(api.searchAvatarResults.value.map((x) => x.id)).toEqual([
|
||||
'avtr_1',
|
||||
'avtr_2'
|
||||
]);
|
||||
expect(api.searchAvatarPage.value.map((x) => x.id)).toEqual([
|
||||
'avtr_1',
|
||||
'avtr_2'
|
||||
]);
|
||||
expect(api.searchAvatarPageNum.value).toBe(0);
|
||||
});
|
||||
|
||||
@@ -60,7 +66,9 @@ describe('useSearchAvatar', () => {
|
||||
|
||||
it('paginates results by 10 items', () => {
|
||||
const api = useSearchAvatar();
|
||||
api.searchAvatarResults.value = Array.from({ length: 25 }, (_, i) => ({ id: `avtr_${i}` }));
|
||||
api.searchAvatarResults.value = Array.from({ length: 25 }, (_, i) => ({
|
||||
id: `avtr_${i}`
|
||||
}));
|
||||
api.searchAvatarPage.value = api.searchAvatarResults.value.slice(0, 10);
|
||||
|
||||
api.moreSearchAvatar(1);
|
||||
|
||||
@@ -38,7 +38,9 @@ describe('useSearchGroup', () => {
|
||||
it('starts group search with normalized query', async () => {
|
||||
mocks.searchText.value = 'group+name';
|
||||
mocks.replaceBioSymbols.mockReturnValue('group name');
|
||||
mocks.groupSearch.mockResolvedValue({ json: [{ id: 'grp_1' }, { id: 'grp_1' }, { id: 'grp_2' }] });
|
||||
mocks.groupSearch.mockResolvedValue({
|
||||
json: [{ id: 'grp_1' }, { id: 'grp_1' }, { id: 'grp_2' }]
|
||||
});
|
||||
|
||||
const api = useSearchGroup();
|
||||
await api.searchGroup();
|
||||
@@ -49,7 +51,10 @@ describe('useSearchGroup', () => {
|
||||
offset: 0,
|
||||
query: 'group name'
|
||||
});
|
||||
expect(api.searchGroupResults.value.map((x) => x.id)).toEqual(['grp_1', 'grp_2']);
|
||||
expect(api.searchGroupResults.value.map((x) => x.id)).toEqual([
|
||||
'grp_1',
|
||||
'grp_2'
|
||||
]);
|
||||
});
|
||||
|
||||
it('moves backward paging offset without going below zero', async () => {
|
||||
@@ -59,6 +64,10 @@ describe('useSearchGroup', () => {
|
||||
|
||||
await api.moreSearchGroup(-1);
|
||||
|
||||
expect(mocks.groupSearch).toHaveBeenCalledWith({ n: 10, offset: 0, query: 'abc' });
|
||||
expect(mocks.groupSearch).toHaveBeenCalledWith({
|
||||
n: 10,
|
||||
offset: 0,
|
||||
query: 'abc'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,10 +44,19 @@ describe('useSearchUser', () => {
|
||||
|
||||
it('passes page direction into handleMoreSearchUser', async () => {
|
||||
const api = useSearchUser();
|
||||
api.searchUserParams.value = { n: 10, offset: 10, search: 'Alice', customFields: 'displayName', sort: 'relevance' };
|
||||
api.searchUserParams.value = {
|
||||
n: 10,
|
||||
offset: 10,
|
||||
search: 'Alice',
|
||||
customFields: 'displayName',
|
||||
sort: 'relevance'
|
||||
};
|
||||
|
||||
await api.handleMoreSearchUser(-1);
|
||||
|
||||
expect(mocks.moreSearchUser).toHaveBeenCalledWith(-1, api.searchUserParams.value);
|
||||
expect(mocks.moreSearchUser).toHaveBeenCalledWith(
|
||||
-1,
|
||||
api.searchUserParams.value
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,7 +49,9 @@ describe('useSearchWorld', () => {
|
||||
mocks.searchText.value = 'home world';
|
||||
mocks.replaceBioSymbols.mockReturnValue('home world');
|
||||
mocks.cachedWorlds.set('wrld_1', { id: 'wrld_1', name: 'World One' });
|
||||
mocks.getWorlds.mockResolvedValue({ json: [{ id: 'wrld_1' }, { id: 'wrld_missing' }] });
|
||||
mocks.getWorlds.mockResolvedValue({
|
||||
json: [{ id: 'wrld_1' }, { id: 'wrld_missing' }]
|
||||
});
|
||||
|
||||
const api = useSearchWorld();
|
||||
api.searchWorld({});
|
||||
@@ -72,7 +74,14 @@ describe('useSearchWorld', () => {
|
||||
|
||||
it('selects category row and uses row sort settings', async () => {
|
||||
mocks.cachedConfig.value = {
|
||||
dynamicWorldRows: [{ index: 2, sortHeading: 'featured', sortOrder: 'ascending', tag: 'party' }]
|
||||
dynamicWorldRows: [
|
||||
{
|
||||
index: 2,
|
||||
sortHeading: 'featured',
|
||||
sortOrder: 'ascending',
|
||||
tag: 'party'
|
||||
}
|
||||
]
|
||||
};
|
||||
mocks.getWorlds.mockResolvedValue({ json: [] });
|
||||
|
||||
|
||||
@@ -511,9 +511,7 @@
|
||||
}
|
||||
|
||||
function handleSelectCustomFont() {
|
||||
const cssVarValue = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--font-western-primary')
|
||||
.trim();
|
||||
const cssVarValue = getComputedStyle(document.documentElement).getPropertyValue('--font-western-primary').trim();
|
||||
const currentKey = String(appFontFamily.value || APP_FONT_DEFAULT_KEY)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
|
||||
@@ -28,7 +28,9 @@ 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) })
|
||||
useAdvancedSettingsStore: () => ({
|
||||
showVRChatConfig: (...a) => mocks.showVRChatConfig(...a)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../SimpleSwitch.vue', () => ({
|
||||
@@ -56,7 +58,13 @@ describe('DiscordPresenceTab.vue', () => {
|
||||
|
||||
const tooltipRow = wrapper
|
||||
.findAll('div.options-container-item')
|
||||
.find((node) => node.text().includes('view.settings.discord_presence.discord_presence.enable_tooltip'));
|
||||
.find((node) =>
|
||||
node
|
||||
.text()
|
||||
.includes(
|
||||
'view.settings.discord_presence.discord_presence.enable_tooltip'
|
||||
)
|
||||
);
|
||||
await tooltipRow.trigger('click');
|
||||
|
||||
expect(mocks.showVRChatConfig).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -12,7 +12,8 @@ vi.mock('../../WristOverlaySettings.vue', () => ({
|
||||
vi.mock('../../../dialogs/FeedFiltersDialog.vue', () => ({
|
||||
default: {
|
||||
props: ['feedFiltersDialogMode'],
|
||||
template: '<div data-testid="feed-dialog" :data-mode="feedFiltersDialogMode" />'
|
||||
template:
|
||||
'<div data-testid="feed-dialog" :data-mode="feedFiltersDialogMode" />'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -22,10 +23,14 @@ 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('');
|
||||
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');
|
||||
expect(
|
||||
wrapper.get('[data-testid="feed-dialog"]').attributes('data-mode')
|
||||
).toBe('wrist');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,9 @@ describe('SimpleSwitch.vue', () => {
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<span data-testid="tooltip"><slot /></span>' }
|
||||
TooltipWrapper: {
|
||||
template: '<span data-testid="tooltip"><slot /></span>'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -45,13 +47,19 @@ describe('SimpleSwitch.vue', () => {
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
TooltipWrapper: { template: '<span data-testid="tooltip"><slot /></span>' }
|
||||
TooltipWrapper: {
|
||||
template: '<span data-testid="tooltip"><slot /></span>'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.get('.name').attributes('style')).toContain('width: 300px');
|
||||
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');
|
||||
expect(
|
||||
wrapper.get('[data-testid="switch"]').attributes('data-disabled')
|
||||
).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,7 +39,9 @@ vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (k) => k }) }));
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useNotificationsSettingsStore: () => mocks.notificationsStore,
|
||||
useWristOverlaySettingsStore: () => mocks.wristStore,
|
||||
useVrStore: () => ({ saveOpenVROption: (...a) => mocks.saveOpenVROption(...a) })
|
||||
useVrStore: () => ({
|
||||
saveOpenVROption: (...a) => mocks.saveOpenVROption(...a)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
|
||||
@@ -111,9 +111,7 @@
|
||||
});
|
||||
|
||||
const currentSharedFeedFilters = computed(() => {
|
||||
return props.feedFiltersDialogMode === 'noty'
|
||||
? sharedFeedFilters.value['noty']
|
||||
: sharedFeedFilters.value['wrist'];
|
||||
return props.feedFiltersDialogMode === 'noty' ? sharedFeedFilters.value['noty'] : sharedFeedFilters.value['wrist'];
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
|
||||
@@ -24,8 +24,10 @@ vi.mock('vue-i18n', () => ({
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
useAvatarProviderStore: () => ({
|
||||
avatarRemoteDatabaseProviderList: mocks.avatarRemoteDatabaseProviderList,
|
||||
saveAvatarProviderList: (...args) => mocks.saveAvatarProviderList(...args),
|
||||
avatarRemoteDatabaseProviderList:
|
||||
mocks.avatarRemoteDatabaseProviderList,
|
||||
saveAvatarProviderList: (...args) =>
|
||||
mocks.saveAvatarProviderList(...args),
|
||||
removeAvatarProvider: (...args) => mocks.removeAvatarProvider(...args)
|
||||
})
|
||||
}));
|
||||
@@ -48,7 +50,8 @@ vi.mock('@/components/ui/dialog', () => ({
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="add-provider" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="add-provider" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -67,7 +70,8 @@ vi.mock('@/components/ui/input-group', () => ({
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
Trash2: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="trash" @click="$emit(\'click\')">trash</button>'
|
||||
template:
|
||||
'<button data-testid="trash" @click="$emit(\'click\')">trash</button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -84,7 +88,10 @@ function mountComponent(props = {}) {
|
||||
|
||||
describe('AvatarProviderDialog.vue', () => {
|
||||
beforeEach(() => {
|
||||
mocks.avatarRemoteDatabaseProviderList.value = ['https://a.example', 'https://b.example'];
|
||||
mocks.avatarRemoteDatabaseProviderList.value = [
|
||||
'https://a.example',
|
||||
'https://b.example'
|
||||
];
|
||||
mocks.saveAvatarProviderList.mockReset();
|
||||
mocks.removeAvatarProvider.mockReset();
|
||||
});
|
||||
@@ -101,7 +108,9 @@ describe('AvatarProviderDialog.vue', () => {
|
||||
|
||||
await wrapper.get('[data-testid="close-dialog"]').trigger('click');
|
||||
|
||||
expect(wrapper.emitted('update:isAvatarProviderDialogVisible')).toEqual([[false]]);
|
||||
expect(wrapper.emitted('update:isAvatarProviderDialogVisible')).toEqual(
|
||||
[[false]]
|
||||
);
|
||||
});
|
||||
|
||||
test('adds empty provider entry when add button clicked', async () => {
|
||||
@@ -122,7 +131,9 @@ describe('AvatarProviderDialog.vue', () => {
|
||||
const input = wrapper.findAll('[data-testid="provider-input"]')[0];
|
||||
await input.setValue('https://updated.example');
|
||||
|
||||
expect(mocks.avatarRemoteDatabaseProviderList.value[0]).toBe('https://updated.example');
|
||||
expect(mocks.avatarRemoteDatabaseProviderList.value[0]).toBe(
|
||||
'https://updated.example'
|
||||
);
|
||||
expect(mocks.saveAvatarProviderList).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -131,6 +142,8 @@ describe('AvatarProviderDialog.vue', () => {
|
||||
|
||||
await wrapper.findAll('[data-testid="trash"]')[1].trigger('click');
|
||||
|
||||
expect(mocks.removeAvatarProvider).toHaveBeenCalledWith('https://b.example');
|
||||
expect(mocks.removeAvatarProvider).toHaveBeenCalledWith(
|
||||
'https://b.example'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,10 +19,9 @@ vi.mock('pinia', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
|
||||
@@ -5,8 +5,8 @@ const mocks = vi.hoisted(() => ({
|
||||
sharedFeedFilters: {
|
||||
__v_isRef: true,
|
||||
value: {
|
||||
noty: { status: 'all' },
|
||||
wrist: { status: 'none' }
|
||||
noty: { status: 'all' },
|
||||
wrist: { status: 'none' }
|
||||
}
|
||||
},
|
||||
photonLoggingEnabled: { __v_isRef: true, value: false },
|
||||
@@ -18,9 +18,15 @@ 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) })
|
||||
usePhotonStore: () => ({
|
||||
photonLoggingEnabled: mocks.photonLoggingEnabled
|
||||
}),
|
||||
useNotificationsSettingsStore: () => ({
|
||||
sharedFeedFilters: mocks.sharedFeedFilters
|
||||
}),
|
||||
useSharedFeedStore: () => ({
|
||||
loadSharedFeed: (...a) => mocks.loadSharedFeed(...a)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../services/config', () => ({
|
||||
@@ -68,7 +74,8 @@ vi.mock('@/components/ui/dialog', () => ({
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
Button: {
|
||||
emits: ['click'],
|
||||
template: '<button data-testid="btn" @click="$emit(\'click\')"><slot /></button>'
|
||||
template:
|
||||
'<button data-testid="btn" @click="$emit(\'click\')"><slot /></button>'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -110,7 +117,9 @@ describe('FeedFiltersDialog.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.get('[data-testid="dialog-title"]').text()).toBe('dialog.shared_feed_filters.wrist');
|
||||
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');
|
||||
@@ -135,7 +144,9 @@ describe('FeedFiltersDialog.vue', () => {
|
||||
const resetButton = wrapper.findAll('[data-testid="btn"]')[0];
|
||||
await resetButton.trigger('click');
|
||||
|
||||
expect(mocks.sharedFeedFilters.value.noty).toEqual({ status: 'default-noty' });
|
||||
expect(mocks.sharedFeedFilters.value.noty).toEqual({
|
||||
status: 'default-noty'
|
||||
});
|
||||
expect(mocks.setString).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,10 +26,9 @@ vi.mock('pinia', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
|
||||
@@ -21,37 +21,123 @@ vi.mock('@vueuse/core', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
vi.mock('../../../stores', () => ({
|
||||
useFriendStore: () => ({ friends: ref(new Map()), isRefreshFriendsLoading: ref(false), onlineFriendCount: ref(0) }),
|
||||
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([]) })
|
||||
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('../../../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('@/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 />' } }));
|
||||
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';
|
||||
|
||||
|
||||
@@ -70,9 +70,7 @@
|
||||
|
||||
const sortedUnseenNotifications = computed(() => [...props.notifications].sort((a, b) => getTs(b) - getTs(a)));
|
||||
|
||||
const sortedRecentNotifications = computed(() =>
|
||||
[...props.recentNotifications].sort((a, b) => getTs(b) - getTs(a))
|
||||
);
|
||||
const sortedRecentNotifications = computed(() => [...props.recentNotifications].sort((a, b) => getTs(b) - getTs(a)));
|
||||
|
||||
const allRows = computed(() => {
|
||||
const rows = [];
|
||||
|
||||
@@ -43,10 +43,9 @@ vi.mock('../../../../shared/utils', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/avatar', () => ({
|
||||
@@ -175,9 +174,7 @@ describe('FriendItem.vue', () => {
|
||||
expect(wrapper.text()).toContain('Ghost');
|
||||
const button = wrapper.get('[data-testid="delete-button"]');
|
||||
await button.trigger('click');
|
||||
expect(mocks.confirmDeleteFriend).toHaveBeenCalledWith(
|
||||
'usr_orphan'
|
||||
);
|
||||
expect(mocks.confirmDeleteFriend).toHaveBeenCalledWith('usr_orphan');
|
||||
expect(mocks.showUserDialog).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,8 @@ const mocks = vi.hoisted(() => ({
|
||||
}
|
||||
]
|
||||
},
|
||||
sortGroupInstancesByInGame: (a, b) => a[0].group.name.localeCompare(b[0].group.name),
|
||||
sortGroupInstancesByInGame: (a, b) =>
|
||||
a[0].group.name.localeCompare(b[0].group.name),
|
||||
showLaunchDialog: vi.fn(),
|
||||
checkCanInviteSelf: vi.fn(() => true),
|
||||
selfInvite: vi.fn().mockResolvedValue({}),
|
||||
@@ -56,11 +57,15 @@ vi.mock('../../../../stores', () => ({
|
||||
sortGroupInstancesByInGame: mocks.sortGroupInstancesByInGame,
|
||||
groupInstances: mocks.groupInstances
|
||||
}),
|
||||
useLaunchStore: () => ({ showLaunchDialog: (...a) => mocks.showLaunchDialog(...a) })
|
||||
useLaunchStore: () => ({
|
||||
showLaunchDialog: (...a) => mocks.showLaunchDialog(...a)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../composables/useInviteChecks', () => ({
|
||||
useInviteChecks: () => ({ checkCanInviteSelf: (...a) => mocks.checkCanInviteSelf(...a) })
|
||||
useInviteChecks: () => ({
|
||||
checkCanInviteSelf: (...a) => mocks.checkCanInviteSelf(...a)
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/utils', () => ({
|
||||
@@ -105,7 +110,10 @@ vi.mock('../../../../components/BackToTop.vue', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../../../components/Location.vue', () => ({
|
||||
default: { props: ['location'], template: '<span data-testid="location">{{ location }}</span>' }
|
||||
default: {
|
||||
props: ['location'],
|
||||
template: '<span data-testid="location">{{ location }}</span>'
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('lucide-vue-next', () => ({
|
||||
@@ -140,6 +148,8 @@ describe('GroupsSidebar.vue', () => {
|
||||
worldId: 'wrld_1',
|
||||
instanceId: '123'
|
||||
});
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith('message.invite.self_sent');
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith(
|
||||
'message.invite.self_sent'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,10 +25,9 @@ vi.mock('vue-router', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores', () => ({
|
||||
@@ -136,9 +135,9 @@ describe('NotificationCenterSheet.vue', () => {
|
||||
mocks.notificationStore.isNotificationCenterOpen.value = true;
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.get('[data-testid="tabs"]').attributes('data-model-value')).toBe(
|
||||
'group'
|
||||
);
|
||||
expect(
|
||||
wrapper.get('[data-testid="tabs"]').attributes('data-model-value')
|
||||
).toBe('group');
|
||||
});
|
||||
|
||||
test('navigate-to-table closes center and routes to notification page', async () => {
|
||||
@@ -150,7 +149,9 @@ describe('NotificationCenterSheet.vue', () => {
|
||||
expect(mocks.notificationStore.isNotificationCenterOpen.value).toBe(
|
||||
false
|
||||
);
|
||||
expect(mocks.router.push).toHaveBeenCalledWith({ name: 'notification' });
|
||||
expect(mocks.router.push).toHaveBeenCalledWith({
|
||||
name: 'notification'
|
||||
});
|
||||
});
|
||||
|
||||
test('show invite response/request dialogs trigger side effects', async () => {
|
||||
@@ -160,21 +161,23 @@ describe('NotificationCenterSheet.vue', () => {
|
||||
.get('[data-testid="emit-show-invite-response"]')
|
||||
.trigger('click');
|
||||
|
||||
expect(mocks.inviteStore.refreshInviteMessageTableData).toHaveBeenCalledWith(
|
||||
'response'
|
||||
);
|
||||
expect(
|
||||
mocks.inviteStore.refreshInviteMessageTableData
|
||||
).toHaveBeenCalledWith('response');
|
||||
expect(mocks.galleryStore.clearInviteImageUpload).toHaveBeenCalled();
|
||||
expect(
|
||||
wrapper.get('[data-testid="dialog-response"]').attributes('data-visible')
|
||||
wrapper
|
||||
.get('[data-testid="dialog-response"]')
|
||||
.attributes('data-visible')
|
||||
).toBe('true');
|
||||
|
||||
await wrapper
|
||||
.get('[data-testid="emit-show-invite-request-response"]')
|
||||
.trigger('click');
|
||||
|
||||
expect(mocks.inviteStore.refreshInviteMessageTableData).toHaveBeenCalledWith(
|
||||
'requestResponse'
|
||||
);
|
||||
expect(
|
||||
mocks.inviteStore.refreshInviteMessageTableData
|
||||
).toHaveBeenCalledWith('requestResponse');
|
||||
expect(
|
||||
wrapper
|
||||
.get('[data-testid="dialog-request-response"]')
|
||||
|
||||
@@ -21,10 +21,9 @@ vi.mock('@tanstack/vue-virtual', () => ({
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key
|
||||
,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
t: (key) => key,
|
||||
locale: require('vue').ref('en')
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('@/components/ui/button', () => ({
|
||||
@@ -83,7 +82,9 @@ describe('NotificationList.vue', () => {
|
||||
makeNoty('old', '2026-03-08T00:00:00.000Z'),
|
||||
makeNoty('new', '2026-03-09T00:00:00.000Z')
|
||||
],
|
||||
recentNotifications: [makeNoty('recent1', '2026-03-07T00:00:00.000Z')]
|
||||
recentNotifications: [
|
||||
makeNoty('recent1', '2026-03-07T00:00:00.000Z')
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -118,7 +119,9 @@ describe('NotificationList.vue', () => {
|
||||
}
|
||||
});
|
||||
|
||||
await wrapper.get('[data-testid="emit-invite-response"]').trigger('click');
|
||||
await wrapper
|
||||
.get('[data-testid="emit-invite-response"]')
|
||||
.trigger('click');
|
||||
await wrapper
|
||||
.get('[data-testid="emit-invite-request-response"]')
|
||||
.trigger('click');
|
||||
|
||||
@@ -195,16 +195,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
CalendarDays,
|
||||
Camera,
|
||||
ChevronDown,
|
||||
Folder,
|
||||
FolderInput,
|
||||
Images,
|
||||
Settings,
|
||||
SquarePen
|
||||
} from 'lucide-vue-next';
|
||||
import { CalendarDays, Camera, ChevronDown, Folder, FolderInput, Images, Settings, SquarePen } from 'lucide-vue-next';
|
||||
import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import ToolItem from './components/ToolItem.vue';
|
||||
|
||||
@@ -8,20 +8,55 @@ const mocks = vi.hoisted(() => ({
|
||||
toastSuccess: vi.fn()
|
||||
}));
|
||||
|
||||
Object.assign(globalThis, { navigator: { clipboard: { writeText: (...a) => mocks.writeText(...a) } } });
|
||||
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' }]]) })
|
||||
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('../../../../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 />' },
|
||||
@@ -34,7 +69,20 @@ 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: '' },
|
||||
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
|
||||
}
|
||||
@@ -55,8 +103,14 @@ describe('GroupCalendarEventCard.vue', () => {
|
||||
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(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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,12 +68,8 @@
|
||||
|
||||
import EditInviteMessageDialog from './EditInviteMessageDialog.vue';
|
||||
|
||||
const {
|
||||
inviteMessageTable,
|
||||
inviteRequestMessageTable,
|
||||
inviteRequestResponseMessageTable,
|
||||
inviteResponseMessageTable
|
||||
} = storeToRefs(useInviteStore());
|
||||
const { inviteMessageTable, inviteRequestMessageTable, inviteRequestResponseMessageTable, inviteResponseMessageTable } =
|
||||
storeToRefs(useInviteStore());
|
||||
const { refreshInviteMessageTableData } = useInviteStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -140,20 +140,32 @@ describe('AutoChangeStatusDialog.vue', () => {
|
||||
.find((node) =>
|
||||
node
|
||||
.attributes('data-label')
|
||||
?.includes('view.settings.general.automation.auto_invite_request_accept')
|
||||
?.includes(
|
||||
'view.settings.general.automation.auto_invite_request_accept'
|
||||
)
|
||||
);
|
||||
|
||||
await autoAcceptSwitch.find('.emit-false').trigger('click');
|
||||
expect(mocks.generalStore.setAutoAcceptInviteRequests).toHaveBeenCalledWith('Off');
|
||||
expect(
|
||||
mocks.generalStore.setAutoAcceptInviteRequests
|
||||
).toHaveBeenCalledWith('Off');
|
||||
|
||||
await autoAcceptSwitch.find('.emit-true').trigger('click');
|
||||
expect(mocks.generalStore.setAutoAcceptInviteRequests).toHaveBeenCalledWith('All Favorites');
|
||||
expect(
|
||||
mocks.generalStore.setAutoAcceptInviteRequests
|
||||
).toHaveBeenCalledWith('All Favorites');
|
||||
|
||||
const noFriendsRadio = wrapper.findAll('[data-testid="radio-group"]')[0];
|
||||
const noFriendsRadio = wrapper.findAll(
|
||||
'[data-testid="radio-group"]'
|
||||
)[0];
|
||||
await noFriendsRadio.find('.emit-false').trigger('click');
|
||||
expect(mocks.generalStore.setAutoStateChangeNoFriends).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mocks.generalStore.setAutoStateChangeNoFriends
|
||||
).not.toHaveBeenCalled();
|
||||
|
||||
await noFriendsRadio.find('.emit-true').trigger('click');
|
||||
expect(mocks.generalStore.setAutoStateChangeNoFriends).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
mocks.generalStore.setAutoStateChangeNoFriends
|
||||
).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user