144 lines
6.1 KiB
TypeScript
144 lines
6.1 KiB
TypeScript
/**
|
||
* 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');
|
||
});
|
||
});
|