mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
410 lines
14 KiB
JavaScript
410 lines
14 KiB
JavaScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { ref } from 'vue';
|
|
import { useAvatarDialogCommands } from '../useAvatarDialogCommands';
|
|
|
|
// Mock external modules
|
|
vi.mock('../../../../api', () => ({
|
|
avatarModerationRequest: {
|
|
sendAvatarModeration: vi.fn(),
|
|
deleteAvatarModeration: vi.fn()
|
|
},
|
|
avatarRequest: {
|
|
saveAvatar: vi.fn(),
|
|
deleteAvatar: vi.fn(),
|
|
selectFallbackAvatar: vi.fn(),
|
|
deleteImposter: vi.fn(),
|
|
createImposter: vi.fn(),
|
|
uploadAvatarImage: vi.fn()
|
|
},
|
|
favoriteRequest: {
|
|
deleteFavorite: vi.fn()
|
|
}
|
|
}));
|
|
|
|
vi.mock('../../../../shared/utils', () => ({
|
|
copyToClipboard: vi.fn(),
|
|
openExternalLink: vi.fn(),
|
|
replaceVrcPackageUrl: vi.fn((url) => url)
|
|
}));
|
|
|
|
vi.mock('../../../../shared/utils/imageUpload', () => ({
|
|
handleImageUploadInput: vi.fn(),
|
|
readFileAsBase64: vi.fn(),
|
|
withUploadTimeout: vi.fn()
|
|
}));
|
|
|
|
vi.mock('../../../../coordinators/imageUploadCoordinator', () => ({
|
|
resizeImageToFitLimits: vi.fn(),
|
|
uploadImageLegacy: vi.fn()
|
|
}));
|
|
|
|
vi.mock('../../../../coordinators/avatarCoordinator', () => ({
|
|
removeAvatarFromCache: vi.fn()
|
|
}));
|
|
|
|
const { copyToClipboard, openExternalLink } =
|
|
await import('../../../../shared/utils');
|
|
const { favoriteRequest, avatarRequest, avatarModerationRequest } =
|
|
await import('../../../../api');
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function createMockAvatarDialog() {
|
|
return ref({
|
|
visible: true,
|
|
loading: false,
|
|
id: 'avtr_test123',
|
|
ref: {
|
|
name: 'TestAvatar',
|
|
description: 'Test desc',
|
|
imageUrl: 'https://example.com/img.png',
|
|
thumbnailImageUrl: 'https://example.com/thumb.png',
|
|
authorId: 'usr_author',
|
|
authorName: 'Author',
|
|
releaseStatus: 'private',
|
|
tags: ['content_horror'],
|
|
unityPackageUrl: 'https://example.com/pkg.unitypackage',
|
|
styles: { primary: 'style1', secondary: 'style2' }
|
|
},
|
|
isBlocked: false,
|
|
hasImposter: false,
|
|
timeSpent: 0,
|
|
galleryLoading: false,
|
|
galleryImages: []
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param overrides
|
|
*/
|
|
function createMockDeps(overrides = {}) {
|
|
return {
|
|
t: vi.fn((key) => key),
|
|
toast: Object.assign(vi.fn(), {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
promise: vi.fn()
|
|
}),
|
|
modalStore: {
|
|
confirm: vi.fn(() => Promise.resolve({ ok: true })),
|
|
prompt: vi.fn(() =>
|
|
Promise.resolve({ ok: true, value: 'new_value' })
|
|
)
|
|
},
|
|
userDialog: ref({ id: 'usr_author' }),
|
|
currentUser: ref({ id: 'usr_current', currentAvatar: 'avtr_other' }),
|
|
cachedAvatars: new Map([
|
|
['avtr_test123', { id: 'avtr_test123', authorId: 'usr_author' }]
|
|
]),
|
|
cachedAvatarModerations: new Map(),
|
|
showAvatarDialog: vi.fn(),
|
|
showFavoriteDialog: vi.fn(),
|
|
applyAvatarModeration: vi.fn((json) => json),
|
|
applyAvatar: vi.fn((json) => json),
|
|
sortUserDialogAvatars: vi.fn(),
|
|
uiStore: { jumpBackDialogCrumb: vi.fn() },
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
describe('useAvatarDialogCommands', () => {
|
|
let avatarDialog;
|
|
let deps;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
avatarDialog = createMockAvatarDialog();
|
|
deps = createMockDeps();
|
|
});
|
|
|
|
describe('direct commands', () => {
|
|
it('Refresh: should call showAvatarDialog with forceRefresh', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Refresh');
|
|
expect(deps.showAvatarDialog).toHaveBeenCalledWith('avtr_test123', {
|
|
forceRefresh: true
|
|
});
|
|
});
|
|
|
|
it('Share: should copy avatar URL', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Share');
|
|
expect(copyToClipboard).toHaveBeenCalledWith(
|
|
'https://vrchat.com/home/avatar/avtr_test123'
|
|
);
|
|
});
|
|
|
|
it('Add Favorite: should call showFavoriteDialog', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Add Favorite');
|
|
expect(deps.showFavoriteDialog).toHaveBeenCalledWith(
|
|
'avatar',
|
|
'avtr_test123'
|
|
);
|
|
});
|
|
|
|
it('Download Unity Package: should open external link', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Download Unity Package');
|
|
expect(openExternalLink).toHaveBeenCalled();
|
|
});
|
|
|
|
it('Rename: should show prompt dialog', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Rename');
|
|
expect(deps.modalStore.prompt).toHaveBeenCalled();
|
|
});
|
|
|
|
it('Change Description: should show prompt dialog', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Change Description');
|
|
expect(deps.modalStore.prompt).toHaveBeenCalled();
|
|
});
|
|
|
|
it('Change Image: triggers file input (DOM-dependent, skip in node)', () => {
|
|
// This command calls document.getElementById which is only available in browser
|
|
// Just verify no error when command is dispatched (DOM interaction tested in e2e)
|
|
if (typeof document === 'undefined') {
|
|
return;
|
|
}
|
|
const mockBtn = { click: vi.fn() };
|
|
vi.spyOn(document, 'getElementById').mockReturnValue(mockBtn);
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Change Image');
|
|
expect(mockBtn.click).toHaveBeenCalled();
|
|
vi.restoreAllMocks();
|
|
});
|
|
});
|
|
|
|
describe('string callback commands', () => {
|
|
it('should delegate to registered callbacks', () => {
|
|
const showSetAvatarTagsDialog = vi.fn();
|
|
const { avatarDialogCommand, registerCallbacks } =
|
|
useAvatarDialogCommands(avatarDialog, deps);
|
|
registerCallbacks({ showSetAvatarTagsDialog });
|
|
avatarDialogCommand('Change Content Tags');
|
|
expect(showSetAvatarTagsDialog).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not throw when callback is not registered', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
expect(() =>
|
|
avatarDialogCommand('Change Content Tags')
|
|
).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('confirmed commands', () => {
|
|
it('Delete Favorite: should confirm then delete', async () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Delete Favorite');
|
|
await vi.waitFor(() => {
|
|
expect(deps.modalStore.confirm).toHaveBeenCalled();
|
|
});
|
|
await vi.waitFor(() => {
|
|
expect(favoriteRequest.deleteFavorite).toHaveBeenCalledWith({
|
|
objectId: 'avtr_test123'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('confirmed command should not execute when cancelled', async () => {
|
|
deps.modalStore.confirm = vi.fn(() =>
|
|
Promise.resolve({ ok: false })
|
|
);
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Delete Favorite');
|
|
await vi.waitFor(() => {
|
|
expect(deps.modalStore.confirm).toHaveBeenCalled();
|
|
});
|
|
expect(favoriteRequest.deleteFavorite).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('Select Fallback Avatar: should confirm then select', async () => {
|
|
avatarRequest.selectFallbackAvatar.mockResolvedValue({ json: {} });
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Select Fallback Avatar');
|
|
await vi.waitFor(() => {
|
|
expect(avatarRequest.selectFallbackAvatar).toHaveBeenCalledWith(
|
|
{ avatarId: 'avtr_test123' }
|
|
);
|
|
});
|
|
});
|
|
|
|
it('Block Avatar: should confirm then send moderation', async () => {
|
|
avatarModerationRequest.sendAvatarModeration.mockResolvedValue({
|
|
json: { targetAvatarId: 'avtr_test123' }
|
|
});
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Block Avatar');
|
|
await vi.waitFor(() => {
|
|
expect(
|
|
avatarModerationRequest.sendAvatarModeration
|
|
).toHaveBeenCalledWith({
|
|
avatarModerationType: 'block',
|
|
targetAvatarId: 'avtr_test123'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Unblock Avatar: should confirm then delete moderation', async () => {
|
|
avatarModerationRequest.deleteAvatarModeration.mockResolvedValue({
|
|
params: {
|
|
targetAvatarId: 'avtr_test123',
|
|
avatarModerationType: 'block'
|
|
}
|
|
});
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Unblock Avatar');
|
|
await vi.waitFor(() => {
|
|
expect(
|
|
avatarModerationRequest.deleteAvatarModeration
|
|
).toHaveBeenCalledWith({
|
|
avatarModerationType: 'block',
|
|
targetAvatarId: 'avtr_test123'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Make Public: should save avatar with public status', async () => {
|
|
avatarRequest.saveAvatar.mockResolvedValue({
|
|
json: { releaseStatus: 'public' }
|
|
});
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Make Public');
|
|
await vi.waitFor(() => {
|
|
expect(avatarRequest.saveAvatar).toHaveBeenCalledWith({
|
|
id: 'avtr_test123',
|
|
releaseStatus: 'public'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Make Private: should save avatar with private status', async () => {
|
|
avatarRequest.saveAvatar.mockResolvedValue({
|
|
json: { releaseStatus: 'private' }
|
|
});
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Make Private');
|
|
await vi.waitFor(() => {
|
|
expect(avatarRequest.saveAvatar).toHaveBeenCalledWith({
|
|
id: 'avtr_test123',
|
|
releaseStatus: 'private'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Delete: should delete avatar and update cache', async () => {
|
|
avatarRequest.deleteAvatar.mockResolvedValue({
|
|
json: { _id: 'avtr_test123', authorId: 'usr_author' }
|
|
});
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Delete');
|
|
await vi.waitFor(() => {
|
|
expect(avatarRequest.deleteAvatar).toHaveBeenCalledWith({
|
|
avatarId: 'avtr_test123'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Create Imposter: should create imposter', async () => {
|
|
avatarRequest.createImposter.mockResolvedValue({ json: {} });
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Create Imposter');
|
|
await vi.waitFor(() => {
|
|
expect(avatarRequest.createImposter).toHaveBeenCalledWith({
|
|
avatarId: 'avtr_test123'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Delete Imposter: should delete imposter and refresh', async () => {
|
|
avatarRequest.deleteImposter.mockResolvedValue({ json: {} });
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
avatarDialogCommand('Delete Imposter');
|
|
await vi.waitFor(() => {
|
|
expect(avatarRequest.deleteImposter).toHaveBeenCalledWith({
|
|
avatarId: 'avtr_test123'
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('unknown command', () => {
|
|
it('should do nothing for unknown commands', () => {
|
|
const { avatarDialogCommand } = useAvatarDialogCommands(
|
|
avatarDialog,
|
|
deps
|
|
);
|
|
expect(() => avatarDialogCommand('NonExistent')).not.toThrow();
|
|
expect(deps.modalStore.confirm).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('image upload state', () => {
|
|
it('should expose crop dialog state refs', () => {
|
|
const { cropDialogOpen, cropDialogFile, changeAvatarImageLoading } =
|
|
useAvatarDialogCommands(avatarDialog, deps);
|
|
expect(cropDialogOpen.value).toBe(false);
|
|
expect(cropDialogFile.value).toBeNull();
|
|
expect(changeAvatarImageLoading.value).toBe(false);
|
|
});
|
|
});
|
|
});
|