mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
refactor: split common until and add ut
This commit is contained in:
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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('');
|
||||||
|
});
|
||||||
|
});
|
||||||
+7
-173
@@ -10,42 +10,19 @@ import {
|
|||||||
useSearchStore,
|
useSearchStore,
|
||||||
useWorldStore
|
useWorldStore
|
||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
|
import {
|
||||||
|
extractFileId,
|
||||||
|
extractFileVersion,
|
||||||
|
extractVariantVersion
|
||||||
|
} from './fileUtils';
|
||||||
import { escapeTag, replaceBioSymbols } from './base/string';
|
import { escapeTag, replaceBioSymbols } from './base/string';
|
||||||
|
import { getFaviconUrl, replaceVrcPackageUrl } from './urlUtils';
|
||||||
import { AppDebug } from '../../service/appConfig.js';
|
import { AppDebug } from '../../service/appConfig.js';
|
||||||
import { compareUnityVersion } from './avatar';
|
import { compareUnityVersion } from './avatar';
|
||||||
|
import { getAvailablePlatforms } from './platformUtils';
|
||||||
import { i18n } from '../../plugin/i18n';
|
import { i18n } from '../../plugin/i18n';
|
||||||
import { miscRequest } from '../../api';
|
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 {string} fileName
|
||||||
* @param {*} data
|
* @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
|
* @param {string} url
|
||||||
@@ -222,130 +181,6 @@ function convertFileUrlToImageUrl(url, resolution = 128) {
|
|||||||
return url;
|
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
|
* @param {string} link
|
||||||
@@ -500,7 +335,6 @@ export {
|
|||||||
extractFileId,
|
extractFileId,
|
||||||
extractFileVersion,
|
extractFileVersion,
|
||||||
extractVariantVersion,
|
extractVariantVersion,
|
||||||
buildTreeData,
|
|
||||||
replaceBioSymbols,
|
replaceBioSymbols,
|
||||||
openExternalLink,
|
openExternalLink,
|
||||||
openDiscordProfile,
|
openDiscordProfile,
|
||||||
|
|||||||
@@ -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 './chart';
|
||||||
export * from './common';
|
export * from './common';
|
||||||
export * from './compare';
|
export * from './compare';
|
||||||
|
export * from './fileUtils';
|
||||||
export * from './friend';
|
export * from './friend';
|
||||||
export * from './group';
|
export * from './group';
|
||||||
export * from './instance';
|
export * from './instance';
|
||||||
|
export * from './platformUtils';
|
||||||
export * from './setting';
|
export * from './setting';
|
||||||
|
export * from './urlUtils';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './gallery';
|
export * from './gallery';
|
||||||
export * from './location';
|
export * from './location';
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -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