mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-21 07:43:50 +02:00
refactor utils
This commit is contained in:
@@ -1,151 +1,154 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
// Mock stores
|
||||
vi.mock('../../../stores', () => ({
|
||||
useFriendStore: vi.fn(),
|
||||
useInstanceStore: vi.fn(),
|
||||
useLocationStore: vi.fn(),
|
||||
useUserStore: vi.fn()
|
||||
}));
|
||||
|
||||
// Mock transitive deps
|
||||
vi.mock('../../../views/Feed/Feed.vue', () => ({
|
||||
default: { template: '<div />' }
|
||||
}));
|
||||
vi.mock('../../../views/Feed/columns.jsx', () => ({ columns: [] }));
|
||||
vi.mock('../../../plugins/router', () => ({
|
||||
default: { push: vi.fn(), currentRoute: { value: {} } }
|
||||
}));
|
||||
|
||||
import {
|
||||
useFriendStore,
|
||||
useInstanceStore,
|
||||
useLocationStore,
|
||||
useUserStore
|
||||
} from '../../../stores';
|
||||
import { checkCanInvite, checkCanInviteSelf } from '../invite';
|
||||
|
||||
const storeMocks = vi.hoisted(() => ({
|
||||
useUserStore: vi.fn(() => ({ currentUser: { id: 'usr_me' } })),
|
||||
useLocationStore: vi.fn(() => ({
|
||||
lastLocation: { location: '', friendList: new Set() }
|
||||
})),
|
||||
useInstanceStore: vi.fn(() => ({ cachedInstances: new Map() })),
|
||||
useFriendStore: vi.fn(() => ({ friends: new Map() }))
|
||||
}));
|
||||
|
||||
vi.mock('../../../stores', () => storeMocks);
|
||||
|
||||
describe('Invite Utils', () => {
|
||||
beforeEach(() => {
|
||||
useUserStore.mockReturnValue({
|
||||
currentUser: { id: 'usr_me' }
|
||||
});
|
||||
useLocationStore.mockReturnValue({
|
||||
lastLocation: { location: 'wrld_last:12345' }
|
||||
});
|
||||
useInstanceStore.mockReturnValue({
|
||||
cachedInstances: new Map()
|
||||
});
|
||||
useFriendStore.mockReturnValue({
|
||||
friends: new Map()
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const defaultInviteDeps = {
|
||||
currentUserId: 'usr_me',
|
||||
lastLocationStr: 'wrld_last:12345',
|
||||
cachedInstances: new Map()
|
||||
};
|
||||
|
||||
const defaultSelfDeps = {
|
||||
currentUserId: 'usr_me',
|
||||
cachedInstances: new Map(),
|
||||
friends: new Map()
|
||||
};
|
||||
|
||||
describe('checkCanInvite', () => {
|
||||
test('does not access stores when deps are provided (pure path)', () => {
|
||||
checkCanInvite('wrld_123:instance', defaultInviteDeps);
|
||||
expect(storeMocks.useUserStore).not.toHaveBeenCalled();
|
||||
expect(storeMocks.useLocationStore).not.toHaveBeenCalled();
|
||||
expect(storeMocks.useInstanceStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('returns false for empty location', () => {
|
||||
expect(checkCanInvite('')).toBe(false);
|
||||
expect(checkCanInvite(null)).toBe(false);
|
||||
expect(checkCanInvite('', defaultInviteDeps)).toBe(false);
|
||||
expect(checkCanInvite(null, defaultInviteDeps)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true for public instance', () => {
|
||||
expect(checkCanInvite('wrld_123:instance')).toBe(true);
|
||||
expect(checkCanInvite('wrld_123:instance', defaultInviteDeps)).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for group instance', () => {
|
||||
expect(
|
||||
checkCanInvite(
|
||||
'wrld_123:instance~group(grp_123)~groupAccessType(public)'
|
||||
'wrld_123:instance~group(grp_123)~groupAccessType(public)',
|
||||
defaultInviteDeps
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for own instance', () => {
|
||||
expect(checkCanInvite('wrld_123:instance~private(usr_me)')).toBe(
|
||||
expect(checkCanInvite('wrld_123:instance~private(usr_me)', defaultInviteDeps)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('returns false for invite-only instance owned by another', () => {
|
||||
expect(checkCanInvite('wrld_123:instance~private(usr_other)')).toBe(
|
||||
expect(checkCanInvite('wrld_123:instance~private(usr_other)', defaultInviteDeps)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('returns false for friends-only instance', () => {
|
||||
expect(checkCanInvite('wrld_123:instance~friends(usr_other)')).toBe(
|
||||
expect(checkCanInvite('wrld_123:instance~friends(usr_other)', defaultInviteDeps)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('returns true for friends+ instance if current location matches', () => {
|
||||
const location = 'wrld_123:instance~hidden(usr_other)';
|
||||
useLocationStore.mockReturnValue({
|
||||
lastLocation: { location }
|
||||
});
|
||||
expect(checkCanInvite(location)).toBe(true);
|
||||
expect(checkCanInvite(location, {
|
||||
...defaultInviteDeps,
|
||||
lastLocationStr: location
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false for friends+ instance if not in that location', () => {
|
||||
expect(checkCanInvite('wrld_123:instance~hidden(usr_other)')).toBe(
|
||||
expect(checkCanInvite('wrld_123:instance~hidden(usr_other)', defaultInviteDeps)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('returns false for closed instance', () => {
|
||||
const location = 'wrld_123:instance';
|
||||
useInstanceStore.mockReturnValue({
|
||||
expect(checkCanInvite(location, {
|
||||
...defaultInviteDeps,
|
||||
cachedInstances: new Map([
|
||||
[location, { closedAt: '2024-01-01' }]
|
||||
])
|
||||
});
|
||||
expect(checkCanInvite(location)).toBe(false);
|
||||
})).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkCanInviteSelf', () => {
|
||||
test('does not access stores when deps are provided (pure path)', () => {
|
||||
checkCanInviteSelf('wrld_123:instance', defaultSelfDeps);
|
||||
expect(storeMocks.useUserStore).not.toHaveBeenCalled();
|
||||
expect(storeMocks.useInstanceStore).not.toHaveBeenCalled();
|
||||
expect(storeMocks.useFriendStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('returns false for empty location', () => {
|
||||
expect(checkCanInviteSelf('')).toBe(false);
|
||||
expect(checkCanInviteSelf(null)).toBe(false);
|
||||
expect(checkCanInviteSelf('', defaultSelfDeps)).toBe(false);
|
||||
expect(checkCanInviteSelf(null, defaultSelfDeps)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true for own instance', () => {
|
||||
expect(
|
||||
checkCanInviteSelf('wrld_123:instance~private(usr_me)')
|
||||
checkCanInviteSelf('wrld_123:instance~private(usr_me)', defaultSelfDeps)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for public instance', () => {
|
||||
expect(checkCanInviteSelf('wrld_123:instance')).toBe(true);
|
||||
expect(checkCanInviteSelf('wrld_123:instance', defaultSelfDeps)).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for friends-only instance if user is a friend', () => {
|
||||
useFriendStore.mockReturnValue({
|
||||
friends: new Map([['usr_owner', {}]])
|
||||
});
|
||||
expect(
|
||||
checkCanInviteSelf('wrld_123:instance~friends(usr_owner)')
|
||||
checkCanInviteSelf('wrld_123:instance~friends(usr_owner)', {
|
||||
...defaultSelfDeps,
|
||||
friends: new Map([['usr_owner', {}]])
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false for friends-only instance if user is not a friend', () => {
|
||||
expect(
|
||||
checkCanInviteSelf('wrld_123:instance~friends(usr_other)')
|
||||
checkCanInviteSelf('wrld_123:instance~friends(usr_other)', defaultSelfDeps)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for closed instance', () => {
|
||||
const location = 'wrld_123:instance';
|
||||
useInstanceStore.mockReturnValue({
|
||||
expect(checkCanInviteSelf(location, {
|
||||
...defaultSelfDeps,
|
||||
cachedInstances: new Map([
|
||||
[location, { closedAt: '2024-01-01' }]
|
||||
])
|
||||
});
|
||||
expect(checkCanInviteSelf(location)).toBe(false);
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true for invite instance (not owned, not closed)', () => {
|
||||
expect(
|
||||
checkCanInviteSelf('wrld_123:instance~private(usr_other)')
|
||||
checkCanInviteSelf('wrld_123:instance~private(usr_other)', defaultSelfDeps)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
// Mock stores
|
||||
vi.mock('../../../stores', () => ({
|
||||
useUserStore: vi.fn(),
|
||||
useAppearanceSettingsStore: vi.fn()
|
||||
}));
|
||||
|
||||
// Mock common.js
|
||||
vi.mock('../common', () => ({
|
||||
convertFileUrlToImageUrl: vi.fn((url) => `converted:${url}`)
|
||||
@@ -21,16 +15,22 @@ vi.mock('../base/ui', () => ({
|
||||
HueToHex: vi.fn((h) => `#hue${h}`)
|
||||
}));
|
||||
|
||||
// Mock transitive deps that get pulled in via stores
|
||||
vi.mock('../../../views/Feed/Feed.vue', () => ({
|
||||
default: { template: '<div />' }
|
||||
}));
|
||||
vi.mock('../../../views/Feed/columns.jsx', () => ({ columns: [] }));
|
||||
vi.mock('../../../plugins/router', () => ({
|
||||
default: { push: vi.fn(), currentRoute: { value: {} } }
|
||||
const storeMocks = vi.hoisted(() => ({
|
||||
useUserStore: vi.fn(() => ({
|
||||
currentUser: {
|
||||
id: 'usr_store',
|
||||
presence: { platform: 'standalonewindows' },
|
||||
onlineFriends: [],
|
||||
activeFriends: []
|
||||
}
|
||||
})),
|
||||
useAppearanceSettingsStore: vi.fn(() => ({
|
||||
displayVRCPlusIconsAsAvatar: false
|
||||
}))
|
||||
}));
|
||||
|
||||
import { useAppearanceSettingsStore, useUserStore } from '../../../stores';
|
||||
vi.mock('../../../stores', () => storeMocks);
|
||||
|
||||
import {
|
||||
languageClass,
|
||||
parseUserUrl,
|
||||
@@ -209,28 +209,42 @@ describe('User Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('userStatusClass (with store mock)', () => {
|
||||
describe('userStatusClass (explicit currentUser)', () => {
|
||||
let currentUser;
|
||||
|
||||
beforeEach(() => {
|
||||
useUserStore.mockReturnValue({
|
||||
currentUser: {
|
||||
id: 'usr_me',
|
||||
presence: { platform: 'standalonewindows' },
|
||||
onlineFriends: [],
|
||||
activeFriends: []
|
||||
}
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
currentUser = {
|
||||
id: 'usr_me',
|
||||
presence: { platform: 'standalonewindows' },
|
||||
onlineFriends: [],
|
||||
activeFriends: []
|
||||
};
|
||||
});
|
||||
|
||||
test('does not access stores when currentUser is passed (pure path)', () => {
|
||||
userStatusClass(
|
||||
{ id: 'usr_me', status: 'active', isFriend: true },
|
||||
false,
|
||||
currentUser
|
||||
);
|
||||
expect(storeMocks.useUserStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('returns null for undefined user', () => {
|
||||
expect(userStatusClass(undefined)).toBeNull();
|
||||
expect(userStatusClass(undefined, false, currentUser)).toBeNull();
|
||||
});
|
||||
|
||||
test('returns current user style with status', () => {
|
||||
const result = userStatusClass({
|
||||
id: 'usr_me',
|
||||
status: 'active',
|
||||
isFriend: true
|
||||
});
|
||||
const result = userStatusClass(
|
||||
{
|
||||
id: 'usr_me',
|
||||
status: 'active',
|
||||
isFriend: true
|
||||
},
|
||||
false,
|
||||
currentUser
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
'status-icon': true,
|
||||
online: true,
|
||||
@@ -239,35 +253,37 @@ describe('User Utils', () => {
|
||||
});
|
||||
|
||||
test('returns mobile true for non-PC platform on current user', () => {
|
||||
useUserStore.mockReturnValue({
|
||||
currentUser: {
|
||||
currentUser.presence = { platform: 'android' };
|
||||
const result = userStatusClass(
|
||||
{
|
||||
id: 'usr_me',
|
||||
presence: { platform: 'android' },
|
||||
onlineFriends: [],
|
||||
activeFriends: []
|
||||
}
|
||||
});
|
||||
const result = userStatusClass({
|
||||
id: 'usr_me',
|
||||
status: 'active'
|
||||
});
|
||||
status: 'active'
|
||||
},
|
||||
false,
|
||||
currentUser
|
||||
);
|
||||
expect(result.mobile).toBe(true);
|
||||
});
|
||||
|
||||
test('returns null for non-friend users', () => {
|
||||
expect(
|
||||
userStatusClass({
|
||||
id: 'usr_other',
|
||||
status: 'active',
|
||||
isFriend: false
|
||||
})
|
||||
userStatusClass(
|
||||
{
|
||||
id: 'usr_other',
|
||||
status: 'active',
|
||||
isFriend: false
|
||||
},
|
||||
false,
|
||||
currentUser
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('returns offline style for pending offline friend', () => {
|
||||
const result = userStatusClass(
|
||||
{ id: 'usr_other', isFriend: true, status: 'active' },
|
||||
true
|
||||
true,
|
||||
currentUser
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
'status-icon': true,
|
||||
@@ -309,7 +325,7 @@ describe('User Utils', () => {
|
||||
status,
|
||||
location,
|
||||
state
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result[expected]).toBe(true);
|
||||
}
|
||||
});
|
||||
@@ -321,7 +337,7 @@ describe('User Utils', () => {
|
||||
status: 'active',
|
||||
location: 'offline',
|
||||
state: ''
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result.offline).toBe(true);
|
||||
});
|
||||
|
||||
@@ -332,7 +348,7 @@ describe('User Utils', () => {
|
||||
status: 'busy',
|
||||
location: 'private',
|
||||
state: 'active'
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result.active).toBe(true);
|
||||
});
|
||||
|
||||
@@ -344,7 +360,7 @@ describe('User Utils', () => {
|
||||
location: 'wrld_1',
|
||||
state: 'online',
|
||||
$platform: 'android'
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result.mobile).toBe(true);
|
||||
});
|
||||
|
||||
@@ -356,7 +372,7 @@ describe('User Utils', () => {
|
||||
location: 'wrld_1',
|
||||
state: 'online',
|
||||
$platform: 'standalonewindows'
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result.mobile).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -364,7 +380,7 @@ describe('User Utils', () => {
|
||||
const result = userStatusClass({
|
||||
userId: 'usr_me',
|
||||
status: 'busy'
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result).toMatchObject({
|
||||
'status-icon': true,
|
||||
busy: true,
|
||||
@@ -373,79 +389,78 @@ describe('User Utils', () => {
|
||||
});
|
||||
|
||||
test('handles private location with empty state (temp fix branch)', () => {
|
||||
useUserStore.mockReturnValue({
|
||||
currentUser: {
|
||||
id: 'usr_me',
|
||||
onlineFriends: [],
|
||||
activeFriends: ['usr_f']
|
||||
}
|
||||
});
|
||||
currentUser.activeFriends = ['usr_f'];
|
||||
const result = userStatusClass({
|
||||
id: 'usr_f',
|
||||
isFriend: true,
|
||||
status: 'busy',
|
||||
location: 'private',
|
||||
state: ''
|
||||
});
|
||||
}, false, currentUser);
|
||||
// activeFriends includes usr_f → active
|
||||
expect(result.active).toBe(true);
|
||||
});
|
||||
|
||||
test('handles private location temp fix → offline branch', () => {
|
||||
useUserStore.mockReturnValue({
|
||||
currentUser: {
|
||||
id: 'usr_me',
|
||||
onlineFriends: [],
|
||||
activeFriends: []
|
||||
}
|
||||
});
|
||||
currentUser.activeFriends = [];
|
||||
const result = userStatusClass({
|
||||
id: 'usr_f',
|
||||
isFriend: true,
|
||||
status: 'busy',
|
||||
location: 'private',
|
||||
state: ''
|
||||
});
|
||||
}, false, currentUser);
|
||||
expect(result.offline).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('userImage (with store mock)', () => {
|
||||
beforeEach(() => {
|
||||
useAppearanceSettingsStore.mockReturnValue({
|
||||
displayVRCPlusIconsAsAvatar: false
|
||||
});
|
||||
describe('userImage (explicit settings)', () => {
|
||||
test('does not access appearance store when setting is passed (pure path)', () => {
|
||||
userImage(
|
||||
{ thumbnailUrl: 'https://img.com/thumb' },
|
||||
false,
|
||||
'128',
|
||||
false,
|
||||
false
|
||||
);
|
||||
expect(storeMocks.useAppearanceSettingsStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('returns empty string for falsy user', () => {
|
||||
expect(userImage(null)).toBe('');
|
||||
expect(userImage(undefined)).toBe('');
|
||||
expect(userImage(null, false, '128', false, false)).toBe('');
|
||||
expect(userImage(undefined, false, '128', false, false)).toBe('');
|
||||
});
|
||||
|
||||
test('returns profilePicOverrideThumbnail when available', () => {
|
||||
const user = {
|
||||
profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb'
|
||||
};
|
||||
expect(userImage(user)).toBe('https://img.com/pic/256/thumb');
|
||||
expect(userImage(user, false, '128', false, false)).toBe(
|
||||
'https://img.com/pic/256/thumb'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces resolution for icon mode with profilePicOverrideThumbnail', () => {
|
||||
const user = {
|
||||
profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb'
|
||||
};
|
||||
expect(userImage(user, true, '64')).toBe(
|
||||
expect(userImage(user, true, '64', false, false)).toBe(
|
||||
'https://img.com/pic/64/thumb'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns profilePicOverride when no thumbnail', () => {
|
||||
const user = { profilePicOverride: 'https://img.com/full' };
|
||||
expect(userImage(user)).toBe('https://img.com/full');
|
||||
expect(userImage(user, false, '128', false, false)).toBe(
|
||||
'https://img.com/full'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns thumbnailUrl as fallback', () => {
|
||||
const user = { thumbnailUrl: 'https://img.com/thumb' };
|
||||
expect(userImage(user)).toBe('https://img.com/thumb');
|
||||
expect(userImage(user, false, '128', false, false)).toBe(
|
||||
'https://img.com/thumb'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns currentAvatarThumbnailImageUrl as fallback', () => {
|
||||
@@ -453,7 +468,9 @@ describe('User Utils', () => {
|
||||
currentAvatarThumbnailImageUrl:
|
||||
'https://img.com/avatar/256/thumb'
|
||||
};
|
||||
expect(userImage(user)).toBe('https://img.com/avatar/256/thumb');
|
||||
expect(userImage(user, false, '128', false, false)).toBe(
|
||||
'https://img.com/avatar/256/thumb'
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces resolution for icon mode with currentAvatarThumbnailImageUrl', () => {
|
||||
@@ -461,7 +478,7 @@ describe('User Utils', () => {
|
||||
currentAvatarThumbnailImageUrl:
|
||||
'https://img.com/avatar/256/thumb'
|
||||
};
|
||||
expect(userImage(user, true, '64')).toBe(
|
||||
expect(userImage(user, true, '64', false, false)).toBe(
|
||||
'https://img.com/avatar/64/thumb'
|
||||
);
|
||||
});
|
||||
@@ -470,39 +487,37 @@ describe('User Utils', () => {
|
||||
const user = {
|
||||
currentAvatarImageUrl: 'https://img.com/avatar/full'
|
||||
};
|
||||
expect(userImage(user)).toBe('https://img.com/avatar/full');
|
||||
expect(userImage(user, false, '128', false, false)).toBe(
|
||||
'https://img.com/avatar/full'
|
||||
);
|
||||
});
|
||||
|
||||
test('converts currentAvatarImageUrl for icon mode', () => {
|
||||
const user = {
|
||||
currentAvatarImageUrl: 'https://img.com/avatar/full'
|
||||
};
|
||||
expect(userImage(user, true)).toBe(
|
||||
expect(userImage(user, true, '128', false, false)).toBe(
|
||||
'converted:https://img.com/avatar/full'
|
||||
);
|
||||
});
|
||||
|
||||
test('returns empty string when user has no image fields', () => {
|
||||
expect(userImage({})).toBe('');
|
||||
expect(userImage({}, false, '128', false, false)).toBe('');
|
||||
});
|
||||
|
||||
test('returns userIcon when displayVRCPlusIconsAsAvatar is true', () => {
|
||||
useAppearanceSettingsStore.mockReturnValue({
|
||||
displayVRCPlusIconsAsAvatar: true
|
||||
});
|
||||
const user = {
|
||||
userIcon: 'https://img.com/icon',
|
||||
thumbnailUrl: 'https://img.com/thumb'
|
||||
};
|
||||
expect(userImage(user)).toBe('https://img.com/icon');
|
||||
expect(userImage(user, false, '128', false, true)).toBe(
|
||||
'https://img.com/icon'
|
||||
);
|
||||
});
|
||||
|
||||
test('converts userIcon for icon mode when VRCPlus setting enabled', () => {
|
||||
useAppearanceSettingsStore.mockReturnValue({
|
||||
displayVRCPlusIconsAsAvatar: true
|
||||
});
|
||||
const user = { userIcon: 'https://img.com/icon' };
|
||||
expect(userImage(user, true)).toBe(
|
||||
expect(userImage(user, true, '128', false, true)).toBe(
|
||||
'converted:https://img.com/icon'
|
||||
);
|
||||
});
|
||||
@@ -512,21 +527,23 @@ describe('User Utils', () => {
|
||||
userIcon: 'https://img.com/icon',
|
||||
thumbnailUrl: 'https://img.com/thumb'
|
||||
};
|
||||
expect(userImage(user, false, '128', true)).toBe(
|
||||
expect(userImage(user, false, '128', true, false)).toBe(
|
||||
'https://img.com/icon'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('userImageFull (with store mock)', () => {
|
||||
beforeEach(() => {
|
||||
useAppearanceSettingsStore.mockReturnValue({
|
||||
displayVRCPlusIconsAsAvatar: false
|
||||
});
|
||||
describe('userImageFull (explicit settings)', () => {
|
||||
test('does not access appearance store when setting is passed (pure path)', () => {
|
||||
userImageFull(
|
||||
{ currentAvatarImageUrl: 'https://img.com/avatar' },
|
||||
false
|
||||
);
|
||||
expect(storeMocks.useAppearanceSettingsStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('returns empty string for falsy user', () => {
|
||||
expect(userImageFull(null)).toBe('');
|
||||
expect(userImageFull(null, false)).toBe('');
|
||||
});
|
||||
|
||||
test('returns profilePicOverride when available', () => {
|
||||
@@ -534,25 +551,22 @@ describe('User Utils', () => {
|
||||
profilePicOverride: 'https://img.com/full',
|
||||
currentAvatarImageUrl: 'https://img.com/avatar'
|
||||
};
|
||||
expect(userImageFull(user)).toBe('https://img.com/full');
|
||||
expect(userImageFull(user, false)).toBe('https://img.com/full');
|
||||
});
|
||||
|
||||
test('returns currentAvatarImageUrl as fallback', () => {
|
||||
const user = {
|
||||
currentAvatarImageUrl: 'https://img.com/avatar'
|
||||
};
|
||||
expect(userImageFull(user)).toBe('https://img.com/avatar');
|
||||
expect(userImageFull(user, false)).toBe('https://img.com/avatar');
|
||||
});
|
||||
|
||||
test('returns userIcon when VRCPlus setting enabled', () => {
|
||||
useAppearanceSettingsStore.mockReturnValue({
|
||||
displayVRCPlusIconsAsAvatar: true
|
||||
});
|
||||
const user = {
|
||||
userIcon: 'https://img.com/icon',
|
||||
profilePicOverride: 'https://img.com/full'
|
||||
};
|
||||
expect(userImageFull(user)).toBe('https://img.com/icon');
|
||||
expect(userImageFull(user, true)).toBe('https://img.com/icon');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { replaceBioSymbols } from './base/string';
|
||||
import { useAuthStore } from '../../stores';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -95,10 +94,9 @@ function getPlatformInfo(unityPackages) {
|
||||
* @param {string} unitySortNumber
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function compareUnityVersion(unitySortNumber) {
|
||||
const authStore = useAuthStore();
|
||||
if (!authStore.cachedConfig.sdkUnityVersion) {
|
||||
console.error('No cachedConfig.sdkUnityVersion');
|
||||
function compareUnityVersion(unitySortNumber, sdkUnityVersion) {
|
||||
if (!sdkUnityVersion) {
|
||||
console.error('No sdkUnityVersion provided');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -106,9 +104,9 @@ function compareUnityVersion(unitySortNumber) {
|
||||
// 2019.4.31f1 2019 04 31 000
|
||||
// 5.3.4p1 5 03 04 010
|
||||
// 2019.4.31f1c1 is a thing
|
||||
const array = authStore.cachedConfig.sdkUnityVersion.split('.');
|
||||
const array = sdkUnityVersion.split('.');
|
||||
if (array.length < 3) {
|
||||
console.error('Invalid cachedConfig.sdkUnityVersion');
|
||||
console.error('Invalid sdkUnityVersion');
|
||||
return false;
|
||||
}
|
||||
let currentUnityVersion = array[0];
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
extractFileVersion,
|
||||
extractVariantVersion
|
||||
} from '../common';
|
||||
import { useAvatarStore, useWorldStore } from '../../../stores';
|
||||
import { useAuthStore, useAvatarStore, useWorldStore } from '../../../stores';
|
||||
import { compareUnityVersion } from '../avatar';
|
||||
|
||||
/**
|
||||
@@ -12,6 +12,8 @@ import { compareUnityVersion } from '../avatar';
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
async function getBundleLocation(input) {
|
||||
const authStore = useAuthStore();
|
||||
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
|
||||
const worldStore = useWorldStore();
|
||||
const avatarStore = useAvatarStore();
|
||||
let unityPackage;
|
||||
@@ -36,7 +38,7 @@ async function getBundleLocation(input) {
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
if (unityPackage.variant !== 'standard') {
|
||||
@@ -59,7 +61,7 @@ async function getBundleLocation(input) {
|
||||
unityPackage = unityPackages[i];
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
import {
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
import { i18n } from '../../../plugins/i18n';
|
||||
import { router } from '../../../plugins/router';
|
||||
import { textToHex } from './string';
|
||||
import { useAppearanceSettingsStore } from '../../../stores';
|
||||
|
||||
import configRepository from '../../../services/config.js';
|
||||
|
||||
@@ -320,11 +318,9 @@ async function refreshCustomScript() {
|
||||
* @param {number} hue
|
||||
* @returns {string}
|
||||
*/
|
||||
function HueToHex(hue) {
|
||||
const appSettingsStore = useAppearanceSettingsStore();
|
||||
const { isDarkMode } = storeToRefs(appSettingsStore);
|
||||
function HueToHex(hue, isDarkMode) {
|
||||
// this.HSVtoRGB(hue / 65535, .8, .8);
|
||||
if (isDarkMode.value) {
|
||||
if (isDarkMode) {
|
||||
return HSVtoRGB(hue / 65535, 0.6, 1);
|
||||
}
|
||||
return HSVtoRGB(hue / 65535, 1, 0.7);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
import {
|
||||
useAuthStore,
|
||||
useAvatarStore,
|
||||
useInstanceStore,
|
||||
useModalStore,
|
||||
@@ -47,6 +48,8 @@ function downloadAndSaveJson(fileName, data) {
|
||||
}
|
||||
|
||||
async function deleteVRChatCache(ref) {
|
||||
const authStore = useAuthStore();
|
||||
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
|
||||
let assetUrl = '';
|
||||
let variant = '';
|
||||
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||
@@ -60,7 +63,7 @@ async function deleteVRChatCache(ref) {
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
if (!unityPackage.variant || unityPackage.variant === 'standard') {
|
||||
@@ -86,6 +89,8 @@ async function checkVRChatCache(ref) {
|
||||
if (!ref.unityPackages) {
|
||||
return { Item1: -1, Item2: false, Item3: '' };
|
||||
}
|
||||
const authStore = useAuthStore();
|
||||
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
|
||||
let assetUrl = '';
|
||||
let variant = '';
|
||||
for (let i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||
@@ -95,7 +100,7 @@ async function checkVRChatCache(ref) {
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)
|
||||
) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
if (!unityPackage.variant || unityPackage.variant === 'standard') {
|
||||
@@ -153,7 +158,7 @@ function copyToClipboard(text, message = 'Copied successfully!') {
|
||||
* @param {number} resolution
|
||||
* @returns {string}
|
||||
*/
|
||||
function convertFileUrlToImageUrl(url, resolution = 128) {
|
||||
function convertFileUrlToImageUrl(url, resolution = 128, endpointDomain = AppDebug.endpointDomain) {
|
||||
if (!url) {
|
||||
return '';
|
||||
}
|
||||
@@ -170,7 +175,7 @@ function convertFileUrlToImageUrl(url, resolution = 128) {
|
||||
if (match) {
|
||||
const fileId = match[1];
|
||||
const version = match[2];
|
||||
return `${AppDebug.endpointDomain}/image/file_${fileId}/${version}/${resolution}`;
|
||||
return `${endpointDomain}/image/file_${fileId}/${version}/${resolution}`;
|
||||
}
|
||||
// no match return origin url
|
||||
return url;
|
||||
@@ -223,6 +228,8 @@ function openDiscordProfile(discordId) {
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function getBundleDateSize(ref) {
|
||||
const authStore = useAuthStore();
|
||||
const sdkUnityVersion = authStore.cachedConfig.sdkUnityVersion;
|
||||
const avatarStore = useAvatarStore();
|
||||
const { avatarDialog } = storeToRefs(avatarStore);
|
||||
const worldStore = useWorldStore();
|
||||
@@ -243,7 +250,7 @@ async function getBundleDateSize(ref) {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (!compareUnityVersion(unityPackage.unitySortNumber)) {
|
||||
if (!compareUnityVersion(unityPackage.unitySortNumber, sdkUnityVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
import {
|
||||
useFriendStore,
|
||||
useInstanceStore,
|
||||
useLocationStore,
|
||||
useUserStore
|
||||
} from '../../stores';
|
||||
import { parseLocation } from './location';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @returns
|
||||
* @param {object} deps
|
||||
* @param {string} deps.currentUserId - current user's id
|
||||
* @param {string} deps.lastLocationStr - last location string from location store
|
||||
* @param {Map} deps.cachedInstances - instance cache map
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkCanInvite(location) {
|
||||
function checkCanInvite(location, deps) {
|
||||
if (!location) {
|
||||
return false;
|
||||
}
|
||||
const userStore = useUserStore();
|
||||
const locationStore = useLocationStore();
|
||||
const instanceStore = useInstanceStore();
|
||||
const L = parseLocation(location);
|
||||
const instance = instanceStore.cachedInstances.get(location);
|
||||
const instance = deps.cachedInstances?.get(location);
|
||||
if (instance?.closedAt) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
L.accessType === 'public' ||
|
||||
L.accessType === 'group' ||
|
||||
L.userId === userStore.currentUser.id
|
||||
L.userId === deps.currentUserId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (L.accessType === 'invite' || L.accessType === 'friends') {
|
||||
return false;
|
||||
}
|
||||
if (locationStore.lastLocation.location === location) {
|
||||
if (deps.lastLocationStr === location) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -42,24 +37,25 @@ function checkCanInvite(location) {
|
||||
/**
|
||||
*
|
||||
* @param {string} location
|
||||
* @returns
|
||||
* @param {object} deps
|
||||
* @param {string} deps.currentUserId - current user's id
|
||||
* @param {Map} deps.cachedInstances - instance cache map
|
||||
* @param {Map} deps.friends - friends map
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkCanInviteSelf(location) {
|
||||
function checkCanInviteSelf(location, deps) {
|
||||
if (!location) {
|
||||
return false;
|
||||
}
|
||||
const userStore = useUserStore();
|
||||
const instanceStore = useInstanceStore();
|
||||
const friendStore = useFriendStore();
|
||||
const L = parseLocation(location);
|
||||
const instance = instanceStore.cachedInstances.get(location);
|
||||
const instance = deps.cachedInstances?.get(location);
|
||||
if (instance?.closedAt) {
|
||||
return false;
|
||||
}
|
||||
if (L.userId === userStore.currentUser.id) {
|
||||
if (L.userId === deps.currentUserId) {
|
||||
return true;
|
||||
}
|
||||
if (L.accessType === 'friends' && !friendStore.friends.has(L.userId)) {
|
||||
if (L.accessType === 'friends' && !deps.friends?.has(L.userId)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { isRealInstance } from './instance.js';
|
||||
import { useLocationStore } from '../../stores/location.js';
|
||||
|
||||
export {
|
||||
parseLocation,
|
||||
@@ -10,10 +9,12 @@ export {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param friendsArr
|
||||
* @param {Array} friendsArr
|
||||
* @param {object} lastLocation - last location from location store
|
||||
* @param {Set} lastLocation.friendList
|
||||
* @param {string} lastLocation.location
|
||||
*/
|
||||
function getFriendsLocations(friendsArr) {
|
||||
const locationStore = useLocationStore();
|
||||
function getFriendsLocations(friendsArr, lastLocation) {
|
||||
// prevent the instance title display as "Traveling".
|
||||
if (!friendsArr?.length) {
|
||||
return '';
|
||||
@@ -28,9 +29,11 @@ function getFriendsLocations(friendsArr) {
|
||||
return friend.ref.travelingToLocation;
|
||||
}
|
||||
}
|
||||
for (const friend of friendsArr) {
|
||||
if (locationStore.lastLocation.friendList.has(friend.id)) {
|
||||
return locationStore.lastLocation.location;
|
||||
if (lastLocation) {
|
||||
for (const friend of friendsArr) {
|
||||
if (lastLocation.friendList.has(friend.id)) {
|
||||
return lastLocation.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
return friendsArr[0].ref?.location;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useAppearanceSettingsStore, useUserStore } from '../../stores';
|
||||
import { HueToHex } from './base/ui';
|
||||
import { convertFileUrlToImageUrl } from './common';
|
||||
import { languageMappings } from '../constants';
|
||||
@@ -40,21 +39,22 @@ function languageClass(language) {
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {boolean} isDarkMode
|
||||
* @returns
|
||||
*/
|
||||
async function getNameColour(userId) {
|
||||
async function getNameColour(userId, isDarkMode) {
|
||||
const hue = await AppApi.GetColourFromUserID(userId);
|
||||
return HueToHex(hue);
|
||||
return HueToHex(hue, isDarkMode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} user
|
||||
* @param {boolean} pendingOffline
|
||||
* @param {object} currentUser - current user object from useUserStore
|
||||
* @returns
|
||||
*/
|
||||
function userStatusClass(user, pendingOffline = false) {
|
||||
const userStore = useUserStore();
|
||||
function userStatusClass(user, pendingOffline = false, currentUser) {
|
||||
const style = {
|
||||
'status-icon': true
|
||||
};
|
||||
@@ -67,8 +67,8 @@ function userStatusClass(user, pendingOffline = false) {
|
||||
} else if (user.userId) {
|
||||
id = user.userId;
|
||||
}
|
||||
if (id === userStore.currentUser.id) {
|
||||
const platform = userStore.currentUser.presence?.platform;
|
||||
if (id === currentUser?.id) {
|
||||
const platform = currentUser.presence?.platform;
|
||||
return {
|
||||
...style,
|
||||
...statusClass(user.status),
|
||||
@@ -89,10 +89,10 @@ function userStatusClass(user, pendingOffline = false) {
|
||||
user.location === 'private' &&
|
||||
user.state === '' &&
|
||||
id &&
|
||||
!userStore.currentUser.onlineFriends.includes(id)
|
||||
!(currentUser?.onlineFriends || []).includes(id)
|
||||
) {
|
||||
// temp fix
|
||||
if (userStore.currentUser.activeFriends.includes(id)) {
|
||||
if ((currentUser?.activeFriends || []).includes(id)) {
|
||||
// Active
|
||||
style.active = true;
|
||||
} else {
|
||||
@@ -166,21 +166,22 @@ function statusClass(status) {
|
||||
* @param {boolean} isIcon - is use for icon (about 40x40)
|
||||
* @param {string} resolution - requested icon resolution (default 128),
|
||||
* @param {boolean} isUserDialogIcon - is use for user dialog icon
|
||||
* @param {boolean} displayVRCPlusIconsAsAvatar - from appearance settings store
|
||||
* @returns {string} - img url
|
||||
*/
|
||||
function userImage(
|
||||
user,
|
||||
isIcon = false,
|
||||
resolution = '128',
|
||||
isUserDialogIcon = false
|
||||
isUserDialogIcon = false,
|
||||
displayVRCPlusIconsAsAvatar = false
|
||||
) {
|
||||
const appAppearanceSettingsStore = useAppearanceSettingsStore();
|
||||
if (!user) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
(isUserDialogIcon && user.userIcon) ||
|
||||
(appAppearanceSettingsStore.displayVRCPlusIconsAsAvatar &&
|
||||
(displayVRCPlusIconsAsAvatar &&
|
||||
user.userIcon)
|
||||
) {
|
||||
if (isIcon) {
|
||||
@@ -225,15 +226,15 @@ function userImage(
|
||||
/**
|
||||
*
|
||||
* @param {object} user
|
||||
* @param {boolean} displayVRCPlusIconsAsAvatar - from appearance settings store
|
||||
* @returns {string|*}
|
||||
*/
|
||||
function userImageFull(user) {
|
||||
function userImageFull(user, displayVRCPlusIconsAsAvatar = false) {
|
||||
if (!user) {
|
||||
return '';
|
||||
}
|
||||
const appAppearanceSettingsStore = useAppearanceSettingsStore();
|
||||
if (
|
||||
appAppearanceSettingsStore.displayVRCPlusIconsAsAvatar &&
|
||||
displayVRCPlusIconsAsAvatar &&
|
||||
user.userIcon
|
||||
) {
|
||||
return user.userIcon;
|
||||
|
||||
Reference in New Issue
Block a user