Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
623 lines
24 KiB
JavaScript
623 lines
24 KiB
JavaScript
/**
|
|
* WidgetLibraryService Tests - TDD (ADR-073)
|
|
* Testing widget library operations for the Widget Picker system
|
|
*/
|
|
|
|
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
import {
|
|
getLibraryWidgets,
|
|
getFavorites,
|
|
getRecent,
|
|
toggleFavorite,
|
|
trackUsage,
|
|
addFromLibrary,
|
|
registerWidget,
|
|
unregisterWidget
|
|
} from '../WidgetLibraryService.js';
|
|
import { createWidget, getWidgetById } from '../WidgetService.js';
|
|
import { dbAll, dbGet, dbRun, destroyAdapter, resetAdapter, toBool } from '../../database/connection.js';
|
|
// Helper functions (matching existing test patterns)
|
|
async function createTestUser(suffix = '') {
|
|
const uniqueEmail = `test-widgetlib-${Date.now()}${suffix}@hltrn.cc`;
|
|
const result = await dbRun(
|
|
'INSERT INTO users (email, password_hash, name, encryption_key_encrypted, email_verified) VALUES (?, ?, ?, ?, ?)',
|
|
[uniqueEmail, 'hash', 'Test User', 'encrypted_key', 1]
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
async function createTestSpace(ownerId, name = 'Test Space') {
|
|
const result = await dbRun(
|
|
'INSERT INTO spaces (owner_id, name, type) VALUES (?, ?, ?)',
|
|
[ownerId, name, 'business']
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
async function createTestProject(ownerId, spaceId) {
|
|
const result = await dbRun(
|
|
'INSERT INTO projects (owner_id, space_id, name, type) VALUES (?, ?, ?, ?)',
|
|
[ownerId, spaceId, 'Test Project', 'business']
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
async function createTestDashboard(projectId) {
|
|
const result = await dbRun(
|
|
'INSERT INTO dashboards (project_id, name, is_default) VALUES (?, ?, ?)',
|
|
[projectId, 'Test Dashboard', toBool(true)]
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
async function createTestTable(spaceId, name = 'Test Table') {
|
|
const result = await dbRun(
|
|
'INSERT INTO universal_tables (space_id, name, type) VALUES (?, ?, ?)',
|
|
[spaceId, name, 'data']
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
async function createTestWidget(dashboardId, options = {}) {
|
|
const {
|
|
presetName = 'table_view',
|
|
title = `Widget ${presetName}`,
|
|
tableId = null
|
|
} = options;
|
|
|
|
const config = tableId ? { table_id: tableId } : {};
|
|
|
|
return await createWidget({
|
|
dashboard_id: dashboardId,
|
|
widget_type: 'preset',
|
|
preset_name: presetName,
|
|
title,
|
|
config,
|
|
position: { x: 0, y: 0, w: 6, h: 4 }
|
|
});
|
|
}
|
|
|
|
async function registerWidgetInLibrary(widgetId, spaceId, options = {}) {
|
|
const {
|
|
is_public = false,
|
|
is_template = false,
|
|
tags = null
|
|
} = options;
|
|
|
|
const result = await dbRun(
|
|
'INSERT INTO widget_library (widget_id, space_id, is_public, is_template, tags) VALUES (?, ?, ?, ?, ?)',
|
|
[widgetId, spaceId, is_public ? 1 : 0, is_template ? 1 : 0, tags ? `{${tags.join(',')}}` : null]
|
|
);
|
|
return result.lastInsertRowid;
|
|
}
|
|
|
|
describe('WidgetLibraryService - ADR-073', () => {
|
|
let userId;
|
|
let spaceId;
|
|
let projectId;
|
|
let dashboardId;
|
|
|
|
beforeEach(async () => {
|
|
process.env.TEST_MODE = 'true';
|
|
process.env.SKIP_DEV_USER = 'true';
|
|
await resetAdapter();
|
|
|
|
userId = await createTestUser();
|
|
spaceId = await createTestSpace(userId);
|
|
projectId = await createTestProject(userId, spaceId);
|
|
dashboardId = await createTestDashboard(projectId);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await destroyAdapter();
|
|
});
|
|
|
|
// ============================================================
|
|
// getLibraryWidgets
|
|
// ============================================================
|
|
describe('getLibraryWidgets', () => {
|
|
test('should return widgets from current space', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId });
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].widget_id).toBe(widget.id);
|
|
expect(result.items[0].is_own_space).toBe(true);
|
|
});
|
|
|
|
test('should include public widgets from other spaces when include_public=true', async () => {
|
|
// Create another space with a public widget
|
|
const otherUserId = await createTestUser('-other');
|
|
const otherSpaceId = await createTestSpace(otherUserId, 'Other Space');
|
|
const otherProjectId = await createTestProject(otherUserId, otherSpaceId);
|
|
const otherDashboardId = await createTestDashboard(otherProjectId);
|
|
const publicWidget = await createTestWidget(otherDashboardId, { title: 'Public Widget' });
|
|
await registerWidgetInLibrary(publicWidget.id, otherSpaceId, { is_public: true });
|
|
|
|
// Create local widget
|
|
const localWidget = await createTestWidget(dashboardId, { title: 'Local Widget' });
|
|
await registerWidgetInLibrary(localWidget.id, spaceId);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, include_public: true });
|
|
|
|
expect(result.items.length).toBeGreaterThanOrEqual(2);
|
|
const widgetIds = result.items.map(w => w.widget_id);
|
|
expect(widgetIds).toContain(publicWidget.id);
|
|
expect(widgetIds).toContain(localWidget.id);
|
|
});
|
|
|
|
test('should not include non-public widgets from other spaces', async () => {
|
|
// Create another space with a private widget
|
|
const otherUserId = await createTestUser('-other2');
|
|
const otherSpaceId = await createTestSpace(otherUserId, 'Other Space 2');
|
|
const otherProjectId = await createTestProject(otherUserId, otherSpaceId);
|
|
const otherDashboardId = await createTestDashboard(otherProjectId);
|
|
const privateWidget = await createTestWidget(otherDashboardId, { title: 'Private Widget' });
|
|
await registerWidgetInLibrary(privateWidget.id, otherSpaceId, { is_public: false });
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, include_public: true });
|
|
|
|
const widgetIds = result.items.map(w => w.widget_id);
|
|
expect(widgetIds).not.toContain(privateWidget.id);
|
|
});
|
|
|
|
test('should filter by category favorites', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Widget 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Widget 2' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
|
|
// Add widget1 to favorites
|
|
await toggleFavorite(userId, widget1.id);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, category: 'favorites' });
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].widget_id).toBe(widget1.id);
|
|
});
|
|
|
|
test('should filter by category recent', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Widget 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Widget 2' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
|
|
// Track usage of widget1
|
|
await trackUsage(userId, widget1.id);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, category: 'recent' });
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].widget_id).toBe(widget1.id);
|
|
});
|
|
|
|
test('should filter by category this_space', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, category: 'this_space' });
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].is_own_space).toBe(true);
|
|
});
|
|
|
|
test('should search by title', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Sales Dashboard' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Tasks Board' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, search: 'Sales' });
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].title).toBe('Sales Dashboard');
|
|
});
|
|
|
|
test('should search by tags', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Widget 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Widget 2' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId, { tags: ['kanban', 'sales'] });
|
|
await registerWidgetInLibrary(widget2.id, spaceId, { tags: ['table', 'contacts'] });
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId, search: 'kanban' });
|
|
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0].widget_id).toBe(widget1.id);
|
|
});
|
|
|
|
test('should include is_favorite flag for current user', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
await toggleFavorite(userId, widget.id);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId });
|
|
|
|
expect(result.items[0].is_favorite).toBe(true);
|
|
});
|
|
|
|
test('should paginate results', async () => {
|
|
// Create 5 widgets
|
|
for (let i = 0; i < 5; i++) {
|
|
const widget = await createTestWidget(dashboardId, { title: `Widget ${i}` });
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
}
|
|
|
|
const page1 = await getLibraryWidgets(spaceId, { userId, limit: 2, offset: 0 });
|
|
const page2 = await getLibraryWidgets(spaceId, { userId, limit: 2, offset: 2 });
|
|
|
|
expect(page1.items).toHaveLength(2);
|
|
expect(page2.items).toHaveLength(2);
|
|
expect(page1.total).toBe(5);
|
|
expect(page2.total).toBe(5);
|
|
});
|
|
|
|
test('should return categories counts', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Widget 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Widget 2' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
await toggleFavorite(userId, widget1.id);
|
|
await trackUsage(userId, widget2.id);
|
|
|
|
const result = await getLibraryWidgets(spaceId, { userId });
|
|
|
|
expect(result.categories).toBeDefined();
|
|
expect(result.categories.favorites).toBe(1);
|
|
expect(result.categories.recent).toBe(1);
|
|
expect(result.categories.this_space).toBe(2);
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// toggleFavorite
|
|
// ============================================================
|
|
describe('toggleFavorite', () => {
|
|
test('should add to favorites if not favorited', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
const result = await toggleFavorite(userId, widget.id);
|
|
|
|
expect(result.is_favorite).toBe(true);
|
|
|
|
// Verify in database
|
|
const fav = await dbGet('SELECT * FROM user_widget_favorites WHERE user_id = ? AND widget_id = ?', [userId, widget.id]);
|
|
expect(fav).toBeDefined();
|
|
});
|
|
|
|
test('should remove from favorites if already favorited', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
// Add to favorites first
|
|
await toggleFavorite(userId, widget.id);
|
|
// Then toggle again
|
|
const result = await toggleFavorite(userId, widget.id);
|
|
|
|
expect(result.is_favorite).toBe(false);
|
|
|
|
// Verify removed from database
|
|
const fav = await dbGet('SELECT * FROM user_widget_favorites WHERE user_id = ? AND widget_id = ?', [userId, widget.id]);
|
|
expect(fav).toBeFalsy();
|
|
});
|
|
|
|
test('should return new favorite status', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
const result1 = await toggleFavorite(userId, widget.id);
|
|
expect(result1.is_favorite).toBe(true);
|
|
|
|
const result2 = await toggleFavorite(userId, widget.id);
|
|
expect(result2.is_favorite).toBe(false);
|
|
});
|
|
|
|
test('should throw error for non-existent widget', async () => {
|
|
await expect(toggleFavorite(userId, 99999))
|
|
.rejects.toThrow('Widget not found');
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// trackUsage
|
|
// ============================================================
|
|
describe('trackUsage', () => {
|
|
test('should increment use_count', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
const before = await dbGet('SELECT use_count FROM widget_library WHERE widget_id = ?', [widget.id]);
|
|
expect(before.use_count).toBe(0);
|
|
|
|
await trackUsage(userId, widget.id);
|
|
|
|
const after = await dbGet('SELECT use_count FROM widget_library WHERE widget_id = ?', [widget.id]);
|
|
expect(after.use_count).toBe(1);
|
|
});
|
|
|
|
test('should update last_used_at', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
const before = await dbGet('SELECT last_used_at FROM widget_library WHERE widget_id = ?', [widget.id]);
|
|
expect(before.last_used_at).toBeNull();
|
|
|
|
await trackUsage(userId, widget.id);
|
|
|
|
const after = await dbGet('SELECT last_used_at FROM widget_library WHERE widget_id = ?', [widget.id]);
|
|
expect(after.last_used_at).not.toBeNull();
|
|
});
|
|
|
|
test('should add entry to user_widget_history', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
await trackUsage(userId, widget.id);
|
|
|
|
const history = await dbGet('SELECT * FROM user_widget_history WHERE user_id = ? AND widget_id = ?', [userId, widget.id]);
|
|
expect(history).toBeDefined();
|
|
expect(history.accessed_at).not.toBeNull();
|
|
});
|
|
|
|
test('should track multiple usages', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
await trackUsage(userId, widget.id);
|
|
await trackUsage(userId, widget.id);
|
|
await trackUsage(userId, widget.id);
|
|
|
|
const lib = await dbGet('SELECT use_count FROM widget_library WHERE widget_id = ?', [widget.id]);
|
|
expect(lib.use_count).toBe(3);
|
|
|
|
const history = await dbAll('SELECT * FROM user_widget_history WHERE user_id = ? AND widget_id = ?', [userId, widget.id]);
|
|
expect(history.length).toBe(3);
|
|
});
|
|
|
|
test('should throw error for non-existent widget', async () => {
|
|
await expect(trackUsage(userId, 99999))
|
|
.rejects.toThrow('Widget not found');
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// getFavorites
|
|
// ============================================================
|
|
describe('getFavorites', () => {
|
|
test('should return user favorites', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Fav 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Not Fav' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
|
|
await toggleFavorite(userId, widget1.id);
|
|
|
|
const favorites = await getFavorites(userId);
|
|
|
|
expect(favorites).toHaveLength(1);
|
|
expect(favorites[0].widget_id).toBe(widget1.id);
|
|
});
|
|
|
|
test('should return empty array when no favorites', async () => {
|
|
const favorites = await getFavorites(userId);
|
|
expect(favorites).toEqual([]);
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// getRecent
|
|
// ============================================================
|
|
describe('getRecent', () => {
|
|
test('should return recently used widgets', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Recent 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Not Recent' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
|
|
await trackUsage(userId, widget1.id);
|
|
|
|
const recent = await getRecent(userId);
|
|
|
|
expect(recent).toHaveLength(1);
|
|
expect(recent[0].widget_id).toBe(widget1.id);
|
|
});
|
|
|
|
test('should respect limit parameter', async () => {
|
|
// Create and track usage for 5 widgets
|
|
for (let i = 0; i < 5; i++) {
|
|
const widget = await createTestWidget(dashboardId, { title: `Widget ${i}` });
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
await trackUsage(userId, widget.id);
|
|
}
|
|
|
|
const recent = await getRecent(userId, 3);
|
|
|
|
expect(recent).toHaveLength(3);
|
|
});
|
|
|
|
test('should return empty array when no recent widgets', async () => {
|
|
const recent = await getRecent(userId);
|
|
expect(recent).toEqual([]);
|
|
});
|
|
|
|
test('should order by most recent first', async () => {
|
|
const widget1 = await createTestWidget(dashboardId, { title: 'Widget 1' });
|
|
const widget2 = await createTestWidget(dashboardId, { title: 'Widget 2' });
|
|
await registerWidgetInLibrary(widget1.id, spaceId);
|
|
await registerWidgetInLibrary(widget2.id, spaceId);
|
|
|
|
await trackUsage(userId, widget1.id);
|
|
// Small delay to ensure different timestamps
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
await trackUsage(userId, widget2.id);
|
|
|
|
const recent = await getRecent(userId);
|
|
|
|
// Most recent (widget2) should be first
|
|
expect(recent[0].widget_id).toBe(widget2.id);
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// addFromLibrary
|
|
// ============================================================
|
|
describe('addFromLibrary', () => {
|
|
test('should create reference widget when mode=reference', async () => {
|
|
const sourceWidget = await createTestWidget(dashboardId, { title: 'Source Widget', presetName: 'kanban_board' });
|
|
await registerWidgetInLibrary(sourceWidget.id, spaceId);
|
|
|
|
// Create another dashboard to add to
|
|
const targetDashboardId = (await dbRun(
|
|
'INSERT INTO dashboards (project_id, name, is_default) VALUES (?, ?, ?)',
|
|
[projectId, 'Target Dashboard', 0]
|
|
)).lastInsertRowid;
|
|
|
|
const result = await addFromLibrary(targetDashboardId, sourceWidget.id, 'reference', { x: 0, y: 0, w: 6, h: 4 }, userId);
|
|
|
|
expect(result.widget).toBeDefined();
|
|
expect(result.widget.source_widget_id).toBe(sourceWidget.id);
|
|
expect(result.widget.dashboard_id).toBe(targetDashboardId);
|
|
expect(result.mode_used).toBe('reference');
|
|
});
|
|
|
|
test('should create copied widget when mode=copy', async () => {
|
|
const sourceWidget = await createTestWidget(dashboardId, { title: 'Source Widget', presetName: 'table_view' });
|
|
await registerWidgetInLibrary(sourceWidget.id, spaceId);
|
|
|
|
const targetDashboardId = (await dbRun(
|
|
'INSERT INTO dashboards (project_id, name, is_default) VALUES (?, ?, ?)',
|
|
[projectId, 'Target Dashboard 2', 0]
|
|
)).lastInsertRowid;
|
|
|
|
const result = await addFromLibrary(targetDashboardId, sourceWidget.id, 'copy', { x: 0, y: 0, w: 6, h: 4 }, userId);
|
|
|
|
expect(result.widget).toBeDefined();
|
|
expect(result.widget.source_widget_id).toBeNull();
|
|
expect(result.widget.title).toBe('Source Widget');
|
|
expect(result.widget.preset_name).toBe('table_view');
|
|
expect(result.mode_used).toBe('copy');
|
|
});
|
|
|
|
test('should track usage on add', async () => {
|
|
const sourceWidget = await createTestWidget(dashboardId, { title: 'Source Widget' });
|
|
await registerWidgetInLibrary(sourceWidget.id, spaceId);
|
|
|
|
const targetDashboardId = (await dbRun(
|
|
'INSERT INTO dashboards (project_id, name, is_default) VALUES (?, ?, ?)',
|
|
[projectId, 'Target Dashboard 3', 0]
|
|
)).lastInsertRowid;
|
|
|
|
await addFromLibrary(targetDashboardId, sourceWidget.id, 'reference', { x: 0, y: 0, w: 6, h: 4 }, userId);
|
|
|
|
const lib = await dbGet('SELECT use_count FROM widget_library WHERE widget_id = ?', [sourceWidget.id]);
|
|
expect(lib.use_count).toBe(1);
|
|
});
|
|
|
|
test('should throw error for non-existent source widget', async () => {
|
|
await expect(addFromLibrary(dashboardId, 99999, 'reference', { x: 0, y: 0, w: 6, h: 4 }, userId))
|
|
.rejects.toThrow('Source widget not found');
|
|
});
|
|
|
|
test('should throw error for non-existent dashboard', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
await expect(addFromLibrary(99999, widget.id, 'reference', { x: 0, y: 0, w: 6, h: 4 }, userId))
|
|
.rejects.toThrow('Dashboard not found');
|
|
});
|
|
|
|
test('should throw error for invalid mode', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidgetInLibrary(widget.id, spaceId);
|
|
|
|
await expect(addFromLibrary(dashboardId, widget.id, 'invalid', { x: 0, y: 0, w: 6, h: 4 }, userId))
|
|
.rejects.toThrow('Invalid mode');
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// registerWidget
|
|
// ============================================================
|
|
describe('registerWidget', () => {
|
|
test('should register widget in library', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
|
|
const result = await registerWidget(widget.id, spaceId);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.widget_id).toBe(widget.id);
|
|
expect(result.space_id).toBe(spaceId);
|
|
});
|
|
|
|
test('should set is_public flag', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
|
|
const result = await registerWidget(widget.id, spaceId, { is_public: true });
|
|
|
|
expect(result.is_public).toBeTruthy();
|
|
});
|
|
|
|
test('should set tags', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
|
|
const result = await registerWidget(widget.id, spaceId, { tags: ['kanban', 'sales'] });
|
|
|
|
const tags = typeof result.tags === 'string' ? JSON.parse(result.tags) : result.tags;
|
|
expect(tags).toEqual(['kanban', 'sales']);
|
|
});
|
|
|
|
test('should throw error for non-existent widget', async () => {
|
|
await expect(registerWidget(99999, spaceId))
|
|
.rejects.toThrow('Widget not found');
|
|
});
|
|
|
|
test('should throw error for non-existent space', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
|
|
await expect(registerWidget(widget.id, 99999))
|
|
.rejects.toThrow('Space not found');
|
|
});
|
|
|
|
test('should throw error if widget already in library', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidget(widget.id, spaceId);
|
|
|
|
await expect(registerWidget(widget.id, spaceId))
|
|
.rejects.toThrow('already in library');
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// unregisterWidget
|
|
// ============================================================
|
|
describe('unregisterWidget', () => {
|
|
test('should remove widget from library', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidget(widget.id, spaceId);
|
|
|
|
await unregisterWidget(widget.id);
|
|
|
|
const lib = await dbGet('SELECT * FROM widget_library WHERE widget_id = ?', [widget.id]);
|
|
expect(lib).toBeFalsy();
|
|
});
|
|
|
|
test('should not delete the widget itself', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
await registerWidget(widget.id, spaceId);
|
|
|
|
await unregisterWidget(widget.id);
|
|
|
|
const w = await getWidgetById(widget.id);
|
|
expect(w).toBeDefined();
|
|
});
|
|
|
|
test('should throw error for widget not in library', async () => {
|
|
const widget = await createTestWidget(dashboardId);
|
|
|
|
await expect(unregisterWidget(widget.id))
|
|
.rejects.toThrow('Widget not in library');
|
|
});
|
|
});
|
|
});
|