refactor: split common until and add ut

This commit is contained in:
pa
2026-03-04 00:45:40 +09:00
parent dd0293d2a6
commit ea82825823
8 changed files with 315 additions and 173 deletions

View File

@@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';
import {
extractFileId,
extractFileVersion,
extractVariantVersion
} from '../fileUtils';
describe('extractFileId', () => {
it('extracts file ID from a URL', () => {
expect(
extractFileId(
'https://api.vrchat.cloud/file/file_abc123-def/1/file'
)
).toBe('file_abc123-def');
});
it('extracts file ID from a plain string', () => {
expect(extractFileId('file_0123456789abcdef')).toBe(
'file_0123456789abcdef'
);
});
it('returns empty string when no match', () => {
expect(extractFileId('no-match-here')).toBe('');
expect(extractFileId('')).toBe('');
});
it('handles null/undefined input', () => {
expect(extractFileId(null)).toBe('');
expect(extractFileId(undefined)).toBe('');
});
});
describe('extractFileVersion', () => {
it('extracts version number from file URL', () => {
expect(extractFileVersion('/file_abc123/5/file')).toBe('5');
});
it('extracts multi-digit version', () => {
expect(extractFileVersion('/file_abc-def-123/123/file')).toBe('123');
});
it('returns empty string when no match', () => {
expect(extractFileVersion('no-version')).toBe('');
expect(extractFileVersion('')).toBe('');
});
});
describe('extractVariantVersion', () => {
it('extracts version from query parameter', () => {
expect(extractVariantVersion('https://example.com/file?v=42')).toBe(
'42'
);
});
it('returns 0 when no v parameter', () => {
expect(extractVariantVersion('https://example.com/file?other=1')).toBe(
'0'
);
});
it('returns 0 for empty/null input', () => {
expect(extractVariantVersion('')).toBe('0');
expect(extractVariantVersion(null)).toBe('0');
expect(extractVariantVersion(undefined)).toBe('0');
});
it('returns 0 for invalid URL', () => {
expect(extractVariantVersion('not-a-url')).toBe('0');
});
});

View File

@@ -0,0 +1,88 @@
import { describe, expect, it } from 'vitest';
import { getAvailablePlatforms } from '../platformUtils';
describe('getAvailablePlatforms', () => {
it('detects PC platform', () => {
const packages = [{ platform: 'standalonewindows' }];
expect(getAvailablePlatforms(packages)).toEqual({
isPC: true,
isQuest: false,
isIos: false
});
});
it('detects Quest (android) platform', () => {
const packages = [{ platform: 'android' }];
expect(getAvailablePlatforms(packages)).toEqual({
isPC: false,
isQuest: true,
isIos: false
});
});
it('detects iOS platform', () => {
const packages = [{ platform: 'ios' }];
expect(getAvailablePlatforms(packages)).toEqual({
isPC: false,
isQuest: false,
isIos: true
});
});
it('detects multiple platforms', () => {
const packages = [
{ platform: 'standalonewindows' },
{ platform: 'android' },
{ platform: 'ios' }
];
expect(getAvailablePlatforms(packages)).toEqual({
isPC: true,
isQuest: true,
isIos: true
});
});
it('skips non-standard/non-security variants', () => {
const packages = [
{ platform: 'standalonewindows', variant: 'custom_variant' },
{ platform: 'android', variant: 'standard' }
];
expect(getAvailablePlatforms(packages)).toEqual({
isPC: false,
isQuest: true,
isIos: false
});
});
it('allows security variant', () => {
const packages = [
{ platform: 'standalonewindows', variant: 'security' }
];
expect(getAvailablePlatforms(packages)).toEqual({
isPC: true,
isQuest: false,
isIos: false
});
});
it('returns all false for empty array', () => {
expect(getAvailablePlatforms([])).toEqual({
isPC: false,
isQuest: false,
isIos: false
});
});
it('returns all false for non-object input', () => {
expect(getAvailablePlatforms('string')).toEqual({
isPC: false,
isQuest: false,
isIos: false
});
});
it('throws for null input (typeof null === "object" but not iterable)', () => {
expect(() => getAvailablePlatforms(null)).toThrow();
});
});

View File

@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest';
import { getFaviconUrl, replaceVrcPackageUrl } from '../urlUtils';
describe('getFaviconUrl', () => {
it('returns favicon URL for valid URL', () => {
expect(getFaviconUrl('https://vrchat.com/home')).toBe(
'https://icons.duckduckgo.com/ip2/vrchat.com.ico'
);
});
it('extracts host from complex URL', () => {
expect(getFaviconUrl('https://store.steampowered.com/app/12345')).toBe(
'https://icons.duckduckgo.com/ip2/store.steampowered.com.ico'
);
});
it('returns empty string for empty input', () => {
expect(getFaviconUrl('')).toBe('');
expect(getFaviconUrl(null)).toBe('');
expect(getFaviconUrl(undefined)).toBe('');
});
it('returns empty string for invalid URL', () => {
expect(getFaviconUrl('not-a-url')).toBe('');
});
});
describe('replaceVrcPackageUrl', () => {
it('replaces api.vrchat.cloud with vrchat.com', () => {
expect(
replaceVrcPackageUrl('https://api.vrchat.cloud/api/1/file/123')
).toBe('https://vrchat.com/api/1/file/123');
});
it('returns URL unchanged if no match', () => {
expect(replaceVrcPackageUrl('https://example.com/test')).toBe(
'https://example.com/test'
);
});
it('returns empty string for empty/null input', () => {
expect(replaceVrcPackageUrl('')).toBe('');
expect(replaceVrcPackageUrl(null)).toBe('');
expect(replaceVrcPackageUrl(undefined)).toBe('');
});
});

View File

@@ -10,42 +10,19 @@ import {
useSearchStore,
useWorldStore
} from '../../stores';
import {
extractFileId,
extractFileVersion,
extractVariantVersion
} from './fileUtils';
import { escapeTag, replaceBioSymbols } from './base/string';
import { getFaviconUrl, replaceVrcPackageUrl } from './urlUtils';
import { AppDebug } from '../../service/appConfig.js';
import { compareUnityVersion } from './avatar';
import { getAvailablePlatforms } from './platformUtils';
import { i18n } from '../../plugin/i18n';
import { miscRequest } from '../../api';
/**
*
* @param {object} unityPackages
* @returns
*/
function getAvailablePlatforms(unityPackages) {
let isPC = false;
let isQuest = false;
let isIos = false;
if (typeof unityPackages === 'object') {
for (const unityPackage of unityPackages) {
if (
unityPackage.variant &&
unityPackage.variant !== 'standard' &&
unityPackage.variant !== 'security'
) {
continue;
}
if (unityPackage.platform === 'standalonewindows') {
isPC = true;
} else if (unityPackage.platform === 'android') {
isQuest = true;
} else if (unityPackage.platform === 'ios') {
isIos = true;
}
}
}
return { isPC, isQuest, isIos };
}
/**
* @param {string} fileName
* @param {*} data
@@ -175,24 +152,6 @@ function copyToClipboard(text, message = 'Copied successfully!') {
});
}
/**
*
* @param {string} resource
* @returns {string}
*/
function getFaviconUrl(resource) {
if (!resource) {
return '';
}
try {
const url = new URL(resource);
return `https://icons.duckduckgo.com/ip2/${url.host}.ico`;
} catch (err) {
console.error('Invalid URL:', resource, err);
return '';
}
}
/**
*
* @param {string} url
@@ -222,130 +181,6 @@ function convertFileUrlToImageUrl(url, resolution = 128) {
return url;
}
/**
*
* @param {string} url
* @returns {string}
*/
function replaceVrcPackageUrl(url) {
if (!url) {
return '';
}
return url.replace('https://api.vrchat.cloud/', 'https://vrchat.com/');
}
/**
*
* @param {string} s
* @returns {string}
*/
function extractFileId(s) {
const match = String(s).match(/file_[0-9A-Za-z-]+/);
return match ? match[0] : '';
}
/**
*
* @param {string} s
* @returns {string}
*/
function extractFileVersion(s) {
const match = /(?:\/file_[0-9A-Za-z-]+\/)([0-9]+)/gi.exec(s);
return match ? match[1] : '';
}
/**
*
* @param {string} url
* @returns {string}
*/
function extractVariantVersion(url) {
if (!url) {
return '0';
}
try {
const params = new URLSearchParams(new URL(url).search);
const version = params.get('v');
if (version) {
return version;
}
return '0';
} catch {
return '0';
}
}
/**
*
* @param {object} json
* @returns {Array}
*/
function buildTreeData(json) {
const node = [];
for (const key in json) {
if (key[0] === '$') {
continue;
}
const value = json[key];
if (Array.isArray(value) && value.length === 0) {
node.push({
key,
value: '[]'
});
} else if (value === Object(value) && Object.keys(value).length === 0) {
node.push({
key,
value: '{}'
});
} else if (Array.isArray(value)) {
node.push({
children: value.map((val, idx) => {
if (val === Object(val)) {
return {
children: buildTreeData(val),
key: idx
};
}
return {
key: idx,
value: val
};
}),
key
});
} else if (value === Object(value)) {
node.push({
children: buildTreeData(value),
key
});
} else {
node.push({
key,
value: String(value)
});
}
}
node.sort(function (a, b) {
const A = String(a.key).toUpperCase();
const B = String(b.key).toUpperCase();
// sort _ to top
if (A.startsWith('_') && !B.startsWith('_')) {
return -1;
}
if (B.startsWith('_') && !A.startsWith('_')) {
return 1;
}
if (A < B) {
return -1;
}
if (A > B) {
return 1;
}
return 0;
});
return node;
}
/**
*
* @param {string} link
@@ -500,7 +335,6 @@ export {
extractFileId,
extractFileVersion,
extractVariantVersion,
buildTreeData,
replaceBioSymbols,
openExternalLink,
openDiscordProfile,

View File

@@ -0,0 +1,39 @@
/**
* @param {string} s
* @returns {string}
*/
function extractFileId(s) {
const match = String(s).match(/file_[0-9A-Za-z-]+/);
return match ? match[0] : '';
}
/**
* @param {string} s
* @returns {string}
*/
function extractFileVersion(s) {
const match = /(?:\/file_[0-9A-Za-z-]+\/)([0-9]+)/gi.exec(s);
return match ? match[1] : '';
}
/**
* @param {string} url
* @returns {string}
*/
function extractVariantVersion(url) {
if (!url) {
return '0';
}
try {
const params = new URLSearchParams(new URL(url).search);
const version = params.get('v');
if (version) {
return version;
}
return '0';
} catch {
return '0';
}
}
export { extractFileId, extractFileVersion, extractVariantVersion };

View File

@@ -7,10 +7,13 @@ export * from './avatar';
export * from './chart';
export * from './common';
export * from './compare';
export * from './fileUtils';
export * from './friend';
export * from './group';
export * from './instance';
export * from './platformUtils';
export * from './setting';
export * from './urlUtils';
export * from './user';
export * from './gallery';
export * from './location';

View File

@@ -0,0 +1,30 @@
/**
* @param {object} unityPackages
* @returns {{ isPC: boolean, isQuest: boolean, isIos: boolean }}
*/
function getAvailablePlatforms(unityPackages) {
let isPC = false;
let isQuest = false;
let isIos = false;
if (typeof unityPackages === 'object') {
for (const unityPackage of unityPackages) {
if (
unityPackage.variant &&
unityPackage.variant !== 'standard' &&
unityPackage.variant !== 'security'
) {
continue;
}
if (unityPackage.platform === 'standalonewindows') {
isPC = true;
} else if (unityPackage.platform === 'android') {
isQuest = true;
} else if (unityPackage.platform === 'ios') {
isIos = true;
}
}
}
return { isPC, isQuest, isIos };
}
export { getAvailablePlatforms };

View File

@@ -0,0 +1,29 @@
/**
* @param {string} resource
* @returns {string}
*/
function getFaviconUrl(resource) {
if (!resource) {
return '';
}
try {
const url = new URL(resource);
return `https://icons.duckduckgo.com/ip2/${url.host}.ico`;
} catch (err) {
console.error('Invalid URL:', resource, err);
return '';
}
}
/**
* @param {string} url
* @returns {string}
*/
function replaceVrcPackageUrl(url) {
if (!url) {
return '';
}
return url.replace('https://api.vrchat.cloud/', 'https://vrchat.com/');
}
export { getFaviconUrl, replaceVrcPackageUrl };