Files
web-cad/frontend/tests/libraryProviders.test.tsx
T

144 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Task F4 LibraryProviderExtension Tests.
*
* Testet die drei Bibliotheksquellen:
* - builtin-catalog (statische JSON-Startersets event/architecture/landscape)
* - user-library (zeichnungsscoped Blöcke via /api/drawings/:id/blocks)
* - global-library (globale Blöcke via /api/global-blocks)
*/
import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { builtinCatalogProvider } from '../src/plugins/builtin/library/providers';
import { makeUserLibraryProvider, makeGlobalLibraryProvider } from '../src/plugins/builtin/library/providers';
import { builtinLibraryPlugin } from '../src/plugins/builtin/library';
import { pluginRegistry } from '../src/plugins/PluginRegistry';
import LibraryProviderPanel from '../src/components/LibraryProviderPanel';
describe('F4: builtin catalog provider', () => {
it('listet genau die 3 Katalog-Ordner', async () => {
const folders = await builtinCatalogProvider.listFolders();
expect(folders.map(f => f.id).sort()).toEqual(['cat-arch', 'cat-event', 'cat-land']);
expect(folders.every(f => f.parentId === null)).toBe(true);
});
it('Startersets: je 15-30 Blöcke mit gültiger Payload und SVG-Thumbnail', async () => {
const folders = await builtinCatalogProvider.listFolders();
for (const folder of folders) {
const blocks = await builtinCatalogProvider.listBlocks(folder.id);
expect(blocks.length).toBeGreaterThanOrEqual(15);
expect(blocks.length).toBeLessThanOrEqual(30);
expect(blocks.every(b => b.folderId === folder.id)).toBe(true);
for (const block of blocks) {
expect(block.thumbnail).toContain('<svg');
expect(block.payload).toBeTruthy();
const elements = JSON.parse(block.payload as string);
expect(Array.isArray(elements)).toBe(true);
expect(elements.length).toBeGreaterThan(0);
for (const el of elements) {
expect(typeof el.id).toBe('string');
expect(typeof el.type).toBe('string');
expect('x' in el && 'y' in el).toBe(true);
expect(el.properties).toBeTypeOf('object');
}
}
}
});
it('listBlocks filtert nach Ordner, getBlock liefert den Block mit Payload', async () => {
const eventBlocks = await builtinCatalogProvider.listBlocks('cat-event');
expect(eventBlocks.length).toBeGreaterThanOrEqual(15);
const first = eventBlocks[0];
const fetched = await builtinCatalogProvider.getBlock(first.id);
expect(fetched.id).toBe(first.id);
expect(fetched.payload).toBe(first.payload);
});
});
describe('F4: Registry-Anbindung', () => {
afterEach(() => {
pluginRegistry.unregisterV2(builtinLibraryPlugin.manifest.id);
});
it('Plugin registriert Provider, enableV2 macht ihn sichtbar, disableV2 entfernt ihn', () => {
pluginRegistry.registerV2(builtinLibraryPlugin);
pluginRegistry.enableV2(builtinLibraryPlugin.manifest.id);
const providers = pluginRegistry.getLibraryProviders();
expect(providers.some(p => p.id === 'builtin-catalog')).toBe(true);
pluginRegistry.disableV2(builtinLibraryPlugin.manifest.id);
expect(pluginRegistry.getLibraryProviders().some(p => p.id === 'builtin-catalog')).toBe(false);
});
});
describe('F4: user-library Provider', () => {
it('mappt zeichnungsscoped Blöcke auf LibBlock mit Thumbnail+Payload', async () => {
const provider = makeUserLibraryProvider([
{
id: 'b1', drawing_id: 'd1', name: 'Bühne A', description: null, category: 'stage',
elements_json: JSON.stringify([
{ id: 'e1', type: 'rect', layerId: 'layer-0', x: 0, y: 0, width: 100, height: 50, properties: { fill: '#3a3a4a' } },
]),
thumbnail: null,
},
]);
const folders = await provider.listFolders();
expect(folders.length).toBe(1);
const blocks = await provider.listBlocks(folders[0].id);
expect(blocks).toHaveLength(1);
expect(blocks[0].name).toBe('Bühne A');
expect(blocks[0].thumbnail).toContain('<svg');
const els = JSON.parse(blocks[0].payload as string);
expect(els[0].id).toBe('e1');
expect((await provider.getBlock(blocks[0].id)).name).toBe('Bühne A');
});
});
describe('F4: global-library Provider', () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it('lädt Ordner und Blöcke der globalen Bibliothek über die API', async () => {
const mockFetch = vi.fn()
.mockResolvedValueOnce({ ok: true, json: async () => [
{ id: 'gf1', name: 'Event', parent_id: null, created_at: '2026-01-01' },
] })
.mockResolvedValueOnce({ ok: true, json: async () => [
{ id: 'gb1', folder_id: 'gf1', name: 'Stuhl-Reihe', block_data: '[]', svg_data: null, is_favorite: false, created_at: '2026-01-01', updated_at: '2026-01-01' },
] });
vi.stubGlobal('fetch', mockFetch);
const provider = makeGlobalLibraryProvider('tok');
const folders = await provider.listFolders();
expect(folders).toHaveLength(1);
expect(folders[0].label).toBe('Event');
const blocks = await provider.listBlocks('gf1');
expect(blocks).toHaveLength(1);
expect(blocks[0].name).toBe('Stuhl-Reihe');
});
});
describe('F4: LibraryProviderPanel UI', () => {
it('rendert Provider-Sektion mit Katalog-Ordnern und Blöcken', async () => {
render(<LibraryProviderPanel providers={[builtinCatalogProvider]} />);
await waitFor(() => {
expect(screen.getAllByText(/Event-Katalog/).length).toBeGreaterThan(0);
});
// Jede Sektion listet Blöcke
expect(screen.getAllByRole('button', { name: /Stuhl/ }).length).toBeGreaterThan(0);
});
it('DragStart legt die Element-Array-Payload in den Datentransfer', async () => {
const setData = vi.fn();
render(<LibraryProviderPanel providers={[builtinCatalogProvider]} />);
const blockBtn = await screen.findByRole('button', { name: /Stuhl/ });
fireEvent.dragStart(blockBtn, { dataTransfer: { setData } });
expect(setData).toHaveBeenCalledWith('text/global-block-data', expect.any(String));
const payload = JSON.parse(setData.mock.calls[0][1] as string);
expect(Array.isArray(payload)).toBe(true);
expect(payload[0].id).toBeTypeOf('string');
});
});