mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-29 19:53:47 +02:00
add unit test
This commit is contained in:
196
src/stores/__tests__/launch.test.js
Normal file
196
src/stores/__tests__/launch.test.js
Normal file
@@ -0,0 +1,196 @@
|
||||
/* eslint-disable pretty-import/sort-import-groups */
|
||||
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import en from '../../localization/en.json';
|
||||
|
||||
vi.mock('../../views/Feed/Feed.vue', () => ({
|
||||
default: { template: '<div />' }
|
||||
}));
|
||||
vi.mock('../../views/Feed/columns.jsx', () => ({ columns: [] }));
|
||||
vi.mock('../../plugin/router', () => ({
|
||||
router: {
|
||||
beforeEach: vi.fn(),
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
currentRoute: ref({ path: '/', name: '', meta: {} }),
|
||||
isReady: vi.fn().mockResolvedValue(true)
|
||||
},
|
||||
initRouter: vi.fn()
|
||||
}));
|
||||
vi.mock('vue-router', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
currentRoute: ref({ path: '/', name: '', meta: {} })
|
||||
}))
|
||||
};
|
||||
});
|
||||
vi.mock('../../plugin/interopApi', () => ({ initInteropApi: vi.fn() }));
|
||||
vi.mock('../../service/database', () => ({
|
||||
database: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, prop) => {
|
||||
if (prop === '__esModule') return false;
|
||||
return vi.fn().mockResolvedValue(null);
|
||||
}
|
||||
}
|
||||
)
|
||||
}));
|
||||
vi.mock('../../service/config', () => ({
|
||||
default: {
|
||||
init: vi.fn(),
|
||||
getString: vi.fn().mockResolvedValue(''),
|
||||
setString: vi.fn(),
|
||||
getBool: vi.fn().mockImplementation((_k, d) => d ?? false),
|
||||
setBool: vi.fn(),
|
||||
getInt: vi.fn().mockImplementation((_k, d) => d ?? 0),
|
||||
setInt: vi.fn(),
|
||||
getFloat: vi.fn().mockImplementation((_k, d) => d ?? 0),
|
||||
setFloat: vi.fn(),
|
||||
getObject: vi.fn().mockReturnValue(null),
|
||||
setObject: vi.fn(),
|
||||
getArray: vi.fn().mockReturnValue([]),
|
||||
setArray: vi.fn(),
|
||||
remove: vi.fn()
|
||||
}
|
||||
}));
|
||||
vi.mock('../../service/jsonStorage', () => ({ default: vi.fn() }));
|
||||
vi.mock('../../service/watchState', () => ({
|
||||
watchState: { isLoggedIn: false }
|
||||
}));
|
||||
vi.mock('vue-i18n', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
const i18n = actual.createI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
legacy: false,
|
||||
missingWarn: false,
|
||||
fallbackWarn: false,
|
||||
messages: { en }
|
||||
});
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => i18n.global
|
||||
};
|
||||
});
|
||||
|
||||
const mockGetInstanceShortName = vi.fn();
|
||||
vi.mock('../../api', () => ({
|
||||
instanceRequest: {
|
||||
getInstanceShortName: (...args) => mockGetInstanceShortName(...args),
|
||||
selfInvite: vi.fn().mockResolvedValue({})
|
||||
},
|
||||
miscRequest: {}
|
||||
}));
|
||||
vi.mock('vue-sonner', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warning: vi.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
import { useLaunchStore } from '../launch';
|
||||
|
||||
describe('useLaunchStore', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
store = useLaunchStore();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('showLaunchDialog', () => {
|
||||
test('sets dialog visible with tag and shortName', async () => {
|
||||
await store.showLaunchDialog(
|
||||
'wrld_123:456~friends(usr_abc)',
|
||||
'abc'
|
||||
);
|
||||
|
||||
expect(store.launchDialogData.visible).toBe(true);
|
||||
expect(store.launchDialogData.tag).toBe(
|
||||
'wrld_123:456~friends(usr_abc)'
|
||||
);
|
||||
expect(store.launchDialogData.shortName).toBe('abc');
|
||||
});
|
||||
|
||||
test('defaults shortName to null', async () => {
|
||||
await store.showLaunchDialog('wrld_123:456');
|
||||
|
||||
expect(store.launchDialogData.shortName).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showLaunchOptions', () => {
|
||||
test('sets isLaunchOptionsDialogVisible to true', () => {
|
||||
expect(store.isLaunchOptionsDialogVisible).toBe(false);
|
||||
store.showLaunchOptions();
|
||||
expect(store.isLaunchOptionsDialogVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLaunchUrl', () => {
|
||||
test('uses provided shortName for non-public instance', async () => {
|
||||
const url = await store.getLaunchUrl(
|
||||
'wrld_123:456~friends(usr_abc)',
|
||||
'myShort'
|
||||
);
|
||||
expect(url).toBe(
|
||||
'vrchat://launch?ref=vrcx.app&id=wrld_123:456~friends(usr_abc)&shortName=myShort'
|
||||
);
|
||||
expect(mockGetInstanceShortName).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('fetches shortName from API when not provided', async () => {
|
||||
mockGetInstanceShortName.mockResolvedValue({
|
||||
json: { shortName: 'fetched123' }
|
||||
});
|
||||
|
||||
const url = await store.getLaunchUrl(
|
||||
'wrld_123:456~friends(usr_abc)',
|
||||
''
|
||||
);
|
||||
expect(url).toContain('shortName=fetched123');
|
||||
expect(mockGetInstanceShortName).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('uses secureName as fallback when shortName is empty', async () => {
|
||||
mockGetInstanceShortName.mockResolvedValue({
|
||||
json: { shortName: '', secureName: 'secure456' }
|
||||
});
|
||||
|
||||
const url = await store.getLaunchUrl(
|
||||
'wrld_123:456~friends(usr_abc)',
|
||||
''
|
||||
);
|
||||
expect(url).toContain('shortName=secure456');
|
||||
});
|
||||
|
||||
test('omits shortName when API returns nothing', async () => {
|
||||
mockGetInstanceShortName.mockResolvedValue({ json: null });
|
||||
|
||||
const url = await store.getLaunchUrl(
|
||||
'wrld_123:456~friends(usr_abc)',
|
||||
''
|
||||
);
|
||||
expect(url).toBe(
|
||||
'vrchat://launch?ref=vrcx.app&id=wrld_123:456~friends(usr_abc)'
|
||||
);
|
||||
expect(url).not.toContain('shortName');
|
||||
});
|
||||
|
||||
test('calls API for public instances without shortName', async () => {
|
||||
mockGetInstanceShortName.mockResolvedValue({ json: null });
|
||||
await store.getLaunchUrl('wrld_123:456', '');
|
||||
expect(mockGetInstanceShortName).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
323
src/stores/__tests__/modal.test.js
Normal file
323
src/stores/__tests__/modal.test.js
Normal file
@@ -0,0 +1,323 @@
|
||||
/* eslint-disable pretty-import/sort-import-groups */
|
||||
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import en from '../../localization/en.json';
|
||||
|
||||
vi.mock('../../views/Feed/Feed.vue', () => ({
|
||||
default: { template: '<div />' }
|
||||
}));
|
||||
vi.mock('../../views/Feed/columns.jsx', () => ({ columns: [] }));
|
||||
vi.mock('../../plugin/router', () => ({
|
||||
router: {
|
||||
beforeEach: vi.fn(),
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
currentRoute: ref({ path: '/', name: '', meta: {} }),
|
||||
isReady: vi.fn().mockResolvedValue(true)
|
||||
},
|
||||
initRouter: vi.fn()
|
||||
}));
|
||||
vi.mock('vue-router', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
replace: vi.fn(),
|
||||
currentRoute: ref({ path: '/', name: '', meta: {} })
|
||||
}))
|
||||
};
|
||||
});
|
||||
vi.mock('../../plugin/interopApi', () => ({ initInteropApi: vi.fn() }));
|
||||
vi.mock('../../service/database', () => ({
|
||||
database: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, prop) => {
|
||||
if (prop === '__esModule') return false;
|
||||
return vi.fn().mockResolvedValue(null);
|
||||
}
|
||||
}
|
||||
)
|
||||
}));
|
||||
vi.mock('../../service/config', () => ({
|
||||
default: {
|
||||
init: vi.fn(),
|
||||
getString: vi.fn().mockImplementation((_k, d) => d ?? '{}'),
|
||||
setString: vi.fn(),
|
||||
getBool: vi.fn().mockImplementation((_k, d) => d ?? false),
|
||||
setBool: vi.fn(),
|
||||
getInt: vi.fn().mockImplementation((_k, d) => d ?? 0),
|
||||
setInt: vi.fn(),
|
||||
getFloat: vi.fn().mockImplementation((_k, d) => d ?? 0),
|
||||
setFloat: vi.fn(),
|
||||
getObject: vi.fn().mockReturnValue(null),
|
||||
setObject: vi.fn(),
|
||||
getArray: vi.fn().mockReturnValue([]),
|
||||
setArray: vi.fn(),
|
||||
remove: vi.fn()
|
||||
}
|
||||
}));
|
||||
vi.mock('../../service/jsonStorage', () => ({ default: vi.fn() }));
|
||||
vi.mock('../../service/watchState', () => ({
|
||||
watchState: { isLoggedIn: false }
|
||||
}));
|
||||
vi.mock('vue-i18n', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
const i18n = actual.createI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
legacy: false,
|
||||
missingWarn: false,
|
||||
fallbackWarn: false,
|
||||
messages: { en }
|
||||
});
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => i18n.global
|
||||
};
|
||||
});
|
||||
|
||||
import { useModalStore } from '../modal';
|
||||
|
||||
describe('useModalStore', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
store = useModalStore();
|
||||
});
|
||||
|
||||
describe('confirm', () => {
|
||||
test('opens dialog and resolves ok:true on handleOk', async () => {
|
||||
const promise = store.confirm({
|
||||
title: 'Delete?',
|
||||
description: 'Are you sure?'
|
||||
});
|
||||
|
||||
expect(store.alertOpen).toBe(true);
|
||||
expect(store.alertMode).toBe('confirm');
|
||||
expect(store.alertTitle).toBe('Delete?');
|
||||
expect(store.alertDescription).toBe('Are you sure?');
|
||||
|
||||
store.handleOk();
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.reason).toBe('ok');
|
||||
expect(store.alertOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('resolves ok:false on handleCancel', async () => {
|
||||
const promise = store.confirm({
|
||||
title: 'Test',
|
||||
description: 'desc'
|
||||
});
|
||||
store.handleCancel();
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.reason).toBe('cancel');
|
||||
expect(store.alertOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('resolves ok:false with reason dismiss on handleDismiss', async () => {
|
||||
const promise = store.confirm({
|
||||
title: 'Test',
|
||||
description: 'desc'
|
||||
});
|
||||
store.handleDismiss();
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.reason).toBe('dismiss');
|
||||
});
|
||||
|
||||
test('does not dismiss when dismissible is false', async () => {
|
||||
const promise = store.confirm({
|
||||
title: 'Test',
|
||||
description: 'desc',
|
||||
dismissible: false
|
||||
});
|
||||
|
||||
expect(store.alertDismissible).toBe(false);
|
||||
store.handleDismiss();
|
||||
expect(store.alertOpen).toBe(true);
|
||||
|
||||
store.handleCancel();
|
||||
await promise;
|
||||
});
|
||||
|
||||
test('uses custom confirmText and cancelText', () => {
|
||||
store.confirm({
|
||||
title: 'T',
|
||||
description: 'D',
|
||||
confirmText: 'Yes',
|
||||
cancelText: 'No'
|
||||
});
|
||||
|
||||
expect(store.alertOkText).toBe('Yes');
|
||||
expect(store.alertCancelText).toBe('No');
|
||||
|
||||
store.handleCancel();
|
||||
});
|
||||
|
||||
test('replaces previous dialog with reason replaced', async () => {
|
||||
const first = store.confirm({
|
||||
title: 'First',
|
||||
description: 'first desc'
|
||||
});
|
||||
const second = store.confirm({
|
||||
title: 'Second',
|
||||
description: 'second desc'
|
||||
});
|
||||
|
||||
const firstResult = await first;
|
||||
expect(firstResult.ok).toBe(false);
|
||||
expect(firstResult.reason).toBe('replaced');
|
||||
|
||||
expect(store.alertTitle).toBe('Second');
|
||||
expect(store.alertOpen).toBe(true);
|
||||
|
||||
store.handleOk();
|
||||
const secondResult = await second;
|
||||
expect(secondResult.ok).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('alert', () => {
|
||||
test('opens in alert mode with only ok button text', () => {
|
||||
store.alert({
|
||||
title: 'Notice',
|
||||
description: 'Something happened'
|
||||
});
|
||||
|
||||
expect(store.alertMode).toBe('alert');
|
||||
expect(store.alertCancelText).toBe('');
|
||||
expect(store.alertOpen).toBe(true);
|
||||
|
||||
store.handleOk();
|
||||
});
|
||||
|
||||
test('handleCancel resolves as ok for alert mode', async () => {
|
||||
const promise = store.alert({
|
||||
title: 'Notice',
|
||||
description: 'desc'
|
||||
});
|
||||
store.handleCancel();
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.reason).toBe('ok');
|
||||
});
|
||||
|
||||
test('handleDismiss resolves as ok for alert mode', async () => {
|
||||
const promise = store.alert({
|
||||
title: 'Notice',
|
||||
description: 'desc'
|
||||
});
|
||||
store.handleDismiss();
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.reason).toBe('ok');
|
||||
});
|
||||
});
|
||||
|
||||
describe('prompt', () => {
|
||||
test('opens prompt dialog with input value', () => {
|
||||
store.prompt({
|
||||
title: 'Enter name',
|
||||
description: 'New name',
|
||||
inputValue: 'default'
|
||||
});
|
||||
|
||||
expect(store.promptOpen).toBe(true);
|
||||
expect(store.promptTitle).toBe('Enter name');
|
||||
expect(store.promptInputValue).toBe('default');
|
||||
|
||||
store.handlePromptCancel('');
|
||||
});
|
||||
|
||||
test('resolves with value on handlePromptOk', async () => {
|
||||
const promise = store.prompt({
|
||||
title: 'T',
|
||||
description: 'D'
|
||||
});
|
||||
|
||||
store.handlePromptOk('myValue');
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.reason).toBe('ok');
|
||||
expect(result.value).toBe('myValue');
|
||||
expect(store.promptOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('resolves with value on handlePromptCancel', async () => {
|
||||
const promise = store.prompt({
|
||||
title: 'T',
|
||||
description: 'D',
|
||||
inputValue: 'initial'
|
||||
});
|
||||
|
||||
store.handlePromptCancel('initial');
|
||||
|
||||
const result = await promise;
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.reason).toBe('cancel');
|
||||
expect(result.value).toBe('initial');
|
||||
});
|
||||
|
||||
test('sets pattern and errorMessage', () => {
|
||||
store.prompt({
|
||||
title: 'T',
|
||||
description: 'D',
|
||||
pattern: /^\d+$/,
|
||||
errorMessage: 'Numbers only'
|
||||
});
|
||||
|
||||
expect(store.promptPattern).toEqual(/^\d+$/);
|
||||
expect(store.promptErrorMessage).toBe('Numbers only');
|
||||
|
||||
store.handlePromptCancel('');
|
||||
});
|
||||
|
||||
test('replaces previous prompt with reason replaced', async () => {
|
||||
const first = store.prompt({
|
||||
title: 'First',
|
||||
description: 'D',
|
||||
inputValue: 'a'
|
||||
});
|
||||
const second = store.prompt({
|
||||
title: 'Second',
|
||||
description: 'D',
|
||||
inputValue: 'b'
|
||||
});
|
||||
|
||||
const firstResult = await first;
|
||||
expect(firstResult.ok).toBe(false);
|
||||
expect(firstResult.reason).toBe('replaced');
|
||||
expect(firstResult.value).toBe('a');
|
||||
|
||||
store.handlePromptOk('b');
|
||||
const secondResult = await second;
|
||||
expect(secondResult.ok).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no-op when no pending', () => {
|
||||
test('handleOk does nothing without pending dialog', () => {
|
||||
store.handleOk();
|
||||
expect(store.alertOpen).toBe(false);
|
||||
});
|
||||
|
||||
test('handlePromptOk does nothing without pending prompt', () => {
|
||||
store.handlePromptOk('test');
|
||||
expect(store.promptOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user