add unit test

This commit is contained in:
pa
2026-02-12 17:21:48 +09:00
parent b12c3d679b
commit fbeb02fb7d
6 changed files with 935 additions and 66 deletions

View 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();
});
});
});

View 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);
});
});
});