mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
refactor: split common until and add ut
This commit is contained in:
72
src/shared/utils/__tests__/fileUtils.test.js
Normal file
72
src/shared/utils/__tests__/fileUtils.test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
88
src/shared/utils/__tests__/platformUtils.test.js
Normal file
88
src/shared/utils/__tests__/platformUtils.test.js
Normal 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();
|
||||
});
|
||||
});
|
||||
47
src/shared/utils/__tests__/urlUtils.test.js
Normal file
47
src/shared/utils/__tests__/urlUtils.test.js
Normal 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('');
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
39
src/shared/utils/fileUtils.js
Normal file
39
src/shared/utils/fileUtils.js
Normal 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 };
|
||||
@@ -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';
|
||||
|
||||
30
src/shared/utils/platformUtils.js
Normal file
30
src/shared/utils/platformUtils.js
Normal 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 };
|
||||
29
src/shared/utils/urlUtils.js
Normal file
29
src/shared/utils/urlUtils.js
Normal 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 };
|
||||
Reference in New Issue
Block a user