/** * Tests for grouping operations * Tests group creation, moving groups, ungrouping */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { get } from 'svelte/store'; import type { Group } from '$lib/api/groups'; import { createGroupFromSelection, canCreateGroup, getGroupColorSuggestions, generateDefaultGroupName, } from '$lib/canvas/operations/group'; import { ungroupImages, removeImagesFromGroup } from '$lib/canvas/operations/ungroup'; import { groups, groupsLoading, groupsError, groupCount } from '$lib/stores/groups'; // Mock API vi.mock('$lib/api/groups', () => ({ createGroup: vi.fn().mockResolvedValue({ id: 'group-1', board_id: 'board-1', name: 'Test Group', color: '#FF5733', annotation: 'Test', member_count: 2, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }), listGroups: vi.fn().mockResolvedValue([]), updateGroup: vi.fn().mockResolvedValue({ id: 'group-1', name: 'Updated', color: '#00FF00', }), deleteGroup: vi.fn().mockResolvedValue(undefined), })); describe('Group Creation', () => { beforeEach(() => { vi.clearAllMocks(); }); it('can create group from selection', () => { const selectedIds = ['img1', 'img2']; expect(canCreateGroup(selectedIds)).toBe(true); }); it('cannot create group with no selection', () => { expect(canCreateGroup([])).toBe(false); }); it('creates group from selection', async () => { const selectedIds = ['img1', 'img2']; const group = await createGroupFromSelection(selectedIds, 'board-1', { name: 'Test Group', color: '#FF5733', annotation: 'Test annotation', }); expect(group).not.toBeNull(); expect(group?.name).toBe('Test Group'); }); it('calls callback on group creation', async () => { const callback = vi.fn(); await createGroupFromSelection(['img1', 'img2'], 'board-1', { name: 'Test Group', color: '#FF5733', onGroupCreate: callback, }); expect(callback).toHaveBeenCalled(); }); it('generates default group names', () => { const existingGroups: Group[] = [ { id: '1', board_id: 'board-1', name: 'Group 1', color: '#FF5733', annotation: null, member_count: 2, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, { id: '2', board_id: 'board-1', name: 'Group 2', color: '#FF5733', annotation: null, member_count: 3, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, ]; const newName = generateDefaultGroupName(existingGroups); expect(newName).toBe('Group 3'); }); it('provides color suggestions', () => { const colors = getGroupColorSuggestions(); expect(colors).toBeInstanceOf(Array); expect(colors.length).toBeGreaterThan(0); expect(colors[0]).toMatch(/^#[0-9A-Fa-f]{6}$/); }); }); describe('Groups Store', () => { beforeEach(() => { groups.clear(); vi.clearAllMocks(); }); it('starts with empty state', () => { const state = get(groups); expect(state.groups).toEqual([]); expect(state.loading).toBe(false); expect(state.error).toBeNull(); }); it('loads groups', async () => { await groups.load('board-1'); expect(get(groupsLoading)).toBe(false); }); it('creates group', async () => { const groupData = { name: 'New Group', color: '#FF5733', image_ids: ['img1', 'img2'], }; const group = await groups.create('board-1', groupData); expect(group).not.toBeNull(); expect(get(groupCount)).toBe(1); }); it('handles creation error', async () => { const { createGroup } = await import('$lib/api/groups'); vi.mocked(createGroup).mockRejectedValueOnce(new Error('API Error')); const group = await groups.create('board-1', { name: 'Test', color: '#FF5733', image_ids: ['img1'], }); expect(group).toBeNull(); expect(get(groupsError)).toBeTruthy(); }); it('deletes group', async () => { // First create a group await groups.create('board-1', { name: 'Test', color: '#FF5733', image_ids: ['img1'], }); expect(get(groupCount)).toBe(1); // Then delete it await groups.delete('board-1', 'group-1'); expect(get(groupCount)).toBe(0); }); it('clears all groups', () => { groups.clear(); const state = get(groups); expect(state.groups).toEqual([]); expect(state.loading).toBe(false); expect(state.error).toBeNull(); }); }); describe('Ungroup Operations', () => { beforeEach(() => { vi.clearAllMocks(); }); it('ungroups images', async () => { const result = await ungroupImages('board-1', 'group-1'); expect(result).toBe(true); }); it('handles ungroup error', async () => { const { deleteGroup } = await import('$lib/api/groups'); vi.mocked(deleteGroup).mockRejectedValueOnce(new Error('API Error')); const result = await ungroupImages('board-1', 'group-1'); expect(result).toBe(false); }); it('removes specific images from group', async () => { vi.mock('$lib/api/client', () => ({ apiClient: { patch: vi.fn().mockResolvedValue({}), }, })); const result = await removeImagesFromGroup('board-1', 'group-1', ['img1', 'img2']); expect(result).toBe(true); }); });