/** * Global Blocks API Service Tests */ import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock fetch globally const mockFetch = vi.fn(); vi.stubGlobal('fetch', mockFetch); // Import after mock is set up import { getGlobalFolders, createGlobalFolder, renameGlobalFolder, deleteGlobalFolder, getGlobalBlocks, createGlobalBlock, renameGlobalBlock, deleteGlobalBlock, } from '../src/services/api'; const TOKEN = 'test-token-123'; function mockResponse(data: unknown, ok = true, status = 200): Response { return { ok, status, json: () => Promise.resolve(data), } as Response; } beforeEach(() => { mockFetch.mockReset(); }); describe('Global Blocks API Service', () => { describe('getGlobalFolders', () => { it('should fetch all folders', async () => { const folders = [{ id: 'f1', name: 'Folder 1', parent_id: null, created_at: '2026-01-01' }]; mockFetch.mockResolvedValue(mockResponse(folders)); const result = await getGlobalFolders(TOKEN); expect(result).toEqual(folders); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/api/global-folders'), expect.objectContaining({ headers: expect.objectContaining({ Authorization: `Bearer ${TOKEN}` }) }), ); }); it('should fetch root folders when parentId=null', async () => { const folders = [{ id: 'f1', name: 'Root', parent_id: null, created_at: '2026-01-01' }]; mockFetch.mockResolvedValue(mockResponse(folders)); const result = await getGlobalFolders(TOKEN, null); expect(result).toEqual(folders); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('parentId=null'), expect.anything(), ); }); it('should fetch sub-folders for a parent', async () => { const folders = [{ id: 'f2', name: 'Sub', parent_id: 'f1', created_at: '2026-01-01' }]; mockFetch.mockResolvedValue(mockResponse(folders)); const result = await getGlobalFolders(TOKEN, 'f1'); expect(result).toEqual(folders); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('parentId=f1'), expect.anything(), ); }); it('should throw on fetch failure', async () => { mockFetch.mockResolvedValue(mockResponse({ error: 'fail' }, false, 500)); await expect(getGlobalFolders(TOKEN)).rejects.toThrow('Failed to load global folders'); }); }); describe('createGlobalFolder', () => { it('should create a folder', async () => { const folder = { id: 'f1', name: 'New Folder', parent_id: null, created_at: '2026-01-01' }; mockFetch.mockResolvedValue(mockResponse(folder, true, 201)); const result = await createGlobalFolder(TOKEN, 'New Folder'); expect(result).toEqual(folder); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/api/global-folders'), expect.objectContaining({ method: 'POST', body: JSON.stringify({ name: 'New Folder', parent_id: null }), }), ); }); it('should create a sub-folder with parentId', async () => { const folder = { id: 'f2', name: 'Sub', parent_id: 'f1', created_at: '2026-01-01' }; mockFetch.mockResolvedValue(mockResponse(folder, true, 201)); const result = await createGlobalFolder(TOKEN, 'Sub', 'f1'); expect(result).toEqual(folder); expect(mockFetch).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ body: JSON.stringify({ name: 'Sub', parent_id: 'f1' }), }), ); }); }); describe('renameGlobalFolder', () => { it('should rename a folder', async () => { const folder = { id: 'f1', name: 'Renamed', parent_id: null, created_at: '2026-01-01' }; mockFetch.mockResolvedValue(mockResponse(folder)); const result = await renameGlobalFolder(TOKEN, 'f1', 'Renamed'); expect(result).toEqual(folder); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/api/global-folders/f1'), expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Renamed' }) }), ); }); }); describe('deleteGlobalFolder', () => { it('should delete a folder', async () => { mockFetch.mockResolvedValue(mockResponse(null, true, 204)); await deleteGlobalFolder(TOKEN, 'f1'); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/api/global-folders/f1'), expect.objectContaining({ method: 'DELETE' }), ); }); }); describe('getGlobalBlocks', () => { it('should fetch all blocks', async () => { const blocks = [{ id: 'b1', folder_id: null, name: 'Block 1', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }]; mockFetch.mockResolvedValue(mockResponse(blocks)); const result = await getGlobalBlocks(TOKEN); expect(result).toEqual(blocks); }); it('should fetch blocks for a folder', async () => { const blocks = [{ id: 'b1', folder_id: 'f1', name: 'Block 1', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }]; mockFetch.mockResolvedValue(mockResponse(blocks)); const result = await getGlobalBlocks(TOKEN, 'f1'); expect(result).toEqual(blocks); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('folderId=f1'), expect.anything(), ); }); it('should fetch root-level blocks with folderId=null', async () => { const blocks = [{ id: 'b1', folder_id: null, name: 'Root Block', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }]; mockFetch.mockResolvedValue(mockResponse(blocks)); const result = await getGlobalBlocks(TOKEN, null); expect(result).toEqual(blocks); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('folderId=null'), expect.anything(), ); }); }); describe('createGlobalBlock', () => { it('should create a block with full data', async () => { const block = { id: 'b1', folder_id: 'f1', name: 'Chair', block_data: '{"type":"chair"}', svg_data: '', created_at: '2026-01-01', updated_at: '2026-01-01' }; mockFetch.mockResolvedValue(mockResponse(block, true, 201)); const result = await createGlobalBlock(TOKEN, { name: 'Chair', folder_id: 'f1', block_data: '{"type":"chair"}', svg_data: '', }); expect(result).toEqual(block); expect(mockFetch).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ method: 'POST', body: JSON.stringify({ name: 'Chair', folder_id: 'f1', block_data: '{"type":"chair"}', svg_data: '', }), }), ); }); }); describe('renameGlobalBlock', () => { it('should rename a block', async () => { const block = { id: 'b1', folder_id: null, name: 'Renamed', block_data: '{}', svg_data: null, created_at: '2026-01-01', updated_at: '2026-01-01' }; mockFetch.mockResolvedValue(mockResponse(block)); const result = await renameGlobalBlock(TOKEN, 'b1', 'Renamed'); expect(result).toEqual(block); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/api/global-blocks/b1'), expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ name: 'Renamed' }) }), ); }); }); describe('deleteGlobalBlock', () => { it('should delete a block', async () => { mockFetch.mockResolvedValue(mockResponse(null, true, 204)); await deleteGlobalBlock(TOKEN, 'b1'); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('/api/global-blocks/b1'), expect.objectContaining({ method: 'DELETE' }), ); }); }); }); /** * GroupTool Extended Tests — getGroupElements method */ import { GroupManager } from '../src/tools/modification/GroupTool'; describe('GroupManager getGroupElements', () => { it('should return only the element itself when not in a group', () => { const gm = new GroupManager(); const result = gm.getGroupElements('el1'); expect(result).toEqual(['el1']); }); it('should return all group members when element is in a group', () => { const gm = new GroupManager(); gm.createGroup(['el1', 'el2', 'el3']); const result = gm.getGroupElements('el1'); expect(result).toHaveLength(3); expect(result).toContain('el1'); expect(result).toContain('el2'); expect(result).toContain('el3'); }); it('should return all group members for any member element', () => { const gm = new GroupManager(); gm.createGroup(['a', 'b', 'c']); expect(gm.getGroupElements('b')).toContain('a'); expect(gm.getGroupElements('b')).toContain('b'); expect(gm.getGroupElements('b')).toContain('c'); expect(gm.getGroupElements('c')).toHaveLength(3); }); it('should return only the element when group was dissolved', () => { const gm = new GroupManager(); const group = gm.createGroup(['x', 'y']); gm.ungroup(group.id); expect(gm.getGroupElements('x')).toEqual(['x']); expect(gm.getGroupElements('y')).toEqual(['y']); }); });