This commit is contained in:
pa
2026-03-06 04:22:16 +09:00
parent 761ef5ad6b
commit 787f25705e
55 changed files with 6437 additions and 506 deletions

View File

@@ -0,0 +1,558 @@
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}`)
}));
// Mock base/format.js
vi.mock('../base/format', () => ({
timeToText: vi.fn((ms) => `${Math.round(ms / 1000)}s`)
}));
// Mock base/ui.js
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('../../../plugin/router', () => ({
default: { push: vi.fn(), currentRoute: { value: {} } }
}));
import { useAppearanceSettingsStore, useUserStore } from '../../../stores';
import {
languageClass,
parseUserUrl,
removeEmojis,
statusClass,
userImage,
userImageFull,
userOnlineFor,
userOnlineForTimestamp,
userStatusClass
} from '../user';
describe('User Utils', () => {
describe('removeEmojis', () => {
test('removes emoji characters from text', () => {
expect(removeEmojis('Hello 🌍 World')).toBe('Hello World');
});
test('collapses multiple spaces after removal', () => {
expect(removeEmojis('A 🎮 B')).toBe('A B');
});
test('returns empty string for falsy input', () => {
expect(removeEmojis('')).toBe('');
expect(removeEmojis(null)).toBe('');
expect(removeEmojis(undefined)).toBe('');
});
test('returns original text when no emojis', () => {
expect(removeEmojis('Hello World')).toBe('Hello World');
});
test('trims whitespace', () => {
expect(removeEmojis(' Hello ')).toBe('Hello');
});
});
describe('statusClass', () => {
test('returns online style for active status', () => {
expect(statusClass('active')).toEqual({
'status-icon': true,
online: true
});
});
test('returns joinme style for join me status', () => {
expect(statusClass('join me')).toEqual({
'status-icon': true,
joinme: true
});
});
test('returns askme style for ask me status', () => {
expect(statusClass('ask me')).toEqual({
'status-icon': true,
askme: true
});
});
test('returns busy style for busy status', () => {
expect(statusClass('busy')).toEqual({
'status-icon': true,
busy: true
});
});
test('returns null for undefined status', () => {
expect(statusClass(undefined)).toBeNull();
});
test('returns null for unknown status strings', () => {
expect(statusClass('offline')).toBeNull();
expect(statusClass('unknown')).toBeNull();
});
});
describe('languageClass', () => {
test('returns mapped flag for known languages', () => {
expect(languageClass('eng')).toEqual({ us: true });
expect(languageClass('jpn')).toEqual({ jp: true });
expect(languageClass('kor')).toEqual({ kr: true });
});
test('returns unknown flag for unmapped languages', () => {
expect(languageClass('xyz')).toEqual({ unknown: true });
expect(languageClass('')).toEqual({ unknown: true });
});
});
describe('parseUserUrl', () => {
test('extracts user ID from VRChat URL', () => {
expect(
parseUserUrl('https://vrchat.com/home/user/usr_abc123-def456')
).toBe('usr_abc123-def456');
});
test('returns undefined for non-user URLs', () => {
expect(
parseUserUrl('https://vrchat.com/home/world/wrld_abc')
).toBeUndefined();
});
test('throws for invalid URLs', () => {
expect(() => parseUserUrl('not-a-url')).toThrow();
});
});
describe('userOnlineForTimestamp', () => {
test('returns ISO date for online user with $online_for', () => {
const ts = Date.now() - 60000;
const ctx = { ref: { state: 'online', $online_for: ts } };
const result = userOnlineForTimestamp(ctx);
expect(result).toBe(new Date(ts).toJSON());
});
test('returns ISO date for active user with $active_for', () => {
const ts = Date.now() - 30000;
const ctx = { ref: { state: 'active', $active_for: ts } };
expect(userOnlineForTimestamp(ctx)).toBe(new Date(ts).toJSON());
});
test('returns ISO date for offline user with $offline_for', () => {
const ts = Date.now() - 120000;
const ctx = {
ref: { state: 'offline', $offline_for: ts }
};
expect(userOnlineForTimestamp(ctx)).toBe(new Date(ts).toJSON());
});
test('returns null when no timestamp available', () => {
const ctx = { ref: { state: 'offline' } };
expect(userOnlineForTimestamp(ctx)).toBeNull();
});
test('prefers $online_for for online state', () => {
const ts1 = Date.now() - 10000;
const ts2 = Date.now() - 50000;
const ctx = {
ref: {
state: 'online',
$online_for: ts1,
$offline_for: ts2
}
};
expect(userOnlineForTimestamp(ctx)).toBe(new Date(ts1).toJSON());
});
});
describe('userOnlineFor', () => {
test('returns formatted time for online user', () => {
const now = Date.now();
vi.spyOn(Date, 'now').mockReturnValue(now);
const ref = { state: 'online', $online_for: now - 5000 };
expect(userOnlineFor(ref)).toBe('5s');
vi.restoreAllMocks();
});
test('returns formatted time for active user', () => {
const now = Date.now();
vi.spyOn(Date, 'now').mockReturnValue(now);
const ref = { state: 'active', $active_for: now - 10000 };
expect(userOnlineFor(ref)).toBe('10s');
vi.restoreAllMocks();
});
test('returns formatted time for offline user with $offline_for', () => {
const now = Date.now();
vi.spyOn(Date, 'now').mockReturnValue(now);
const ref = { state: 'offline', $offline_for: now - 3000 };
expect(userOnlineFor(ref)).toBe('3s');
vi.restoreAllMocks();
});
test('returns dash when no timestamp available', () => {
expect(userOnlineFor({ state: 'offline' })).toBe('-');
});
});
describe('userStatusClass (with store mock)', () => {
beforeEach(() => {
useUserStore.mockReturnValue({
currentUser: {
id: 'usr_me',
presence: { platform: 'standalonewindows' },
onlineFriends: [],
activeFriends: []
}
});
});
test('returns null for undefined user', () => {
expect(userStatusClass(undefined)).toBeNull();
});
test('returns current user style with status', () => {
const result = userStatusClass({
id: 'usr_me',
status: 'active',
isFriend: true
});
expect(result).toMatchObject({
'status-icon': true,
online: true,
mobile: false
});
});
test('returns mobile true for non-PC platform on current user', () => {
useUserStore.mockReturnValue({
currentUser: {
id: 'usr_me',
presence: { platform: 'android' },
onlineFriends: [],
activeFriends: []
}
});
const result = userStatusClass({
id: 'usr_me',
status: 'active'
});
expect(result.mobile).toBe(true);
});
test('returns null for non-friend users', () => {
expect(
userStatusClass({
id: 'usr_other',
status: 'active',
isFriend: false
})
).toBeNull();
});
test('returns offline style for pending offline friend', () => {
const result = userStatusClass(
{ id: 'usr_other', isFriend: true, status: 'active' },
true
);
expect(result).toMatchObject({
'status-icon': true,
offline: true
});
});
test('returns correct style for each friend status', () => {
const cases = [
{
status: 'active',
location: 'wrld_1',
state: 'online',
expected: 'online'
},
{
status: 'join me',
location: 'wrld_1',
state: 'online',
expected: 'joinme'
},
{
status: 'ask me',
location: 'wrld_1',
state: 'online',
expected: 'askme'
},
{
status: 'busy',
location: 'wrld_1',
state: 'online',
expected: 'busy'
}
];
for (const { status, location, state, expected } of cases) {
const result = userStatusClass({
id: 'usr_friend',
isFriend: true,
status,
location,
state
});
expect(result[expected]).toBe(true);
}
});
test('returns offline style for location offline', () => {
const result = userStatusClass({
id: 'usr_f',
isFriend: true,
status: 'active',
location: 'offline',
state: ''
});
expect(result.offline).toBe(true);
});
test('returns active style for state active', () => {
const result = userStatusClass({
id: 'usr_f',
isFriend: true,
status: 'busy',
location: 'private',
state: 'active'
});
expect(result.active).toBe(true);
});
test('sets mobile flag for non-PC platform friend', () => {
const result = userStatusClass({
id: 'usr_f',
isFriend: true,
status: 'active',
location: 'wrld_1',
state: 'online',
$platform: 'android'
});
expect(result.mobile).toBe(true);
});
test('no mobile flag for standalonewindows platform', () => {
const result = userStatusClass({
id: 'usr_f',
isFriend: true,
status: 'active',
location: 'wrld_1',
state: 'online',
$platform: 'standalonewindows'
});
expect(result.mobile).toBeUndefined();
});
test('uses userId as fallback when id is not present', () => {
const result = userStatusClass({
userId: 'usr_me',
status: 'busy'
});
expect(result).toMatchObject({
'status-icon': true,
busy: true,
mobile: false
});
});
test('handles private location with empty state (temp fix branch)', () => {
useUserStore.mockReturnValue({
currentUser: {
id: 'usr_me',
onlineFriends: [],
activeFriends: ['usr_f']
}
});
const result = userStatusClass({
id: 'usr_f',
isFriend: true,
status: 'busy',
location: 'private',
state: ''
});
// 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: []
}
});
const result = userStatusClass({
id: 'usr_f',
isFriend: true,
status: 'busy',
location: 'private',
state: ''
});
expect(result.offline).toBe(true);
});
});
describe('userImage (with store mock)', () => {
beforeEach(() => {
useAppearanceSettingsStore.mockReturnValue({
displayVRCPlusIconsAsAvatar: false
});
});
test('returns empty string for falsy user', () => {
expect(userImage(null)).toBe('');
expect(userImage(undefined)).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');
});
test('replaces resolution for icon mode with profilePicOverrideThumbnail', () => {
const user = {
profilePicOverrideThumbnail: 'https://img.com/pic/256/thumb'
};
expect(userImage(user, true, '64')).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');
});
test('returns thumbnailUrl as fallback', () => {
const user = { thumbnailUrl: 'https://img.com/thumb' };
expect(userImage(user)).toBe('https://img.com/thumb');
});
test('returns currentAvatarThumbnailImageUrl as fallback', () => {
const user = {
currentAvatarThumbnailImageUrl:
'https://img.com/avatar/256/thumb'
};
expect(userImage(user)).toBe('https://img.com/avatar/256/thumb');
});
test('replaces resolution for icon mode with currentAvatarThumbnailImageUrl', () => {
const user = {
currentAvatarThumbnailImageUrl:
'https://img.com/avatar/256/thumb'
};
expect(userImage(user, true, '64')).toBe(
'https://img.com/avatar/64/thumb'
);
});
test('returns currentAvatarImageUrl as last resort', () => {
const user = {
currentAvatarImageUrl: 'https://img.com/avatar/full'
};
expect(userImage(user)).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(
'converted:https://img.com/avatar/full'
);
});
test('returns empty string when user has no image fields', () => {
expect(userImage({})).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');
});
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(
'converted:https://img.com/icon'
);
});
test('returns userIcon for isUserDialogIcon even if VRCPlus setting off', () => {
const user = {
userIcon: 'https://img.com/icon',
thumbnailUrl: 'https://img.com/thumb'
};
expect(userImage(user, false, '128', true)).toBe(
'https://img.com/icon'
);
});
});
describe('userImageFull (with store mock)', () => {
beforeEach(() => {
useAppearanceSettingsStore.mockReturnValue({
displayVRCPlusIconsAsAvatar: false
});
});
test('returns empty string for falsy user', () => {
expect(userImageFull(null)).toBe('');
});
test('returns profilePicOverride when available', () => {
const user = {
profilePicOverride: 'https://img.com/full',
currentAvatarImageUrl: 'https://img.com/avatar'
};
expect(userImageFull(user)).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');
});
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');
});
});
});