@svadmin/ui 0.33.1 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
3
+ import TaskQueueDrawer from './TaskQueueDrawer.svelte';
4
+ const taskFixtures = [
5
+ {
6
+ id: 'task-1',
7
+ title: 'Generate poster',
8
+ status: 'running',
9
+ message: 'Rendering hero artwork',
10
+ createdAt: '2026-04-19T10:00:00.000Z',
11
+ updatedAt: '2026-04-19T10:05:00.000Z',
12
+ progress: 42,
13
+ },
14
+ {
15
+ id: 'task-2',
16
+ title: 'Send email batch',
17
+ status: 'queued',
18
+ message: 'Waiting for worker',
19
+ createdAt: '2026-04-19T10:10:00.000Z',
20
+ updatedAt: '2026-04-19T10:12:00.000Z',
21
+ },
22
+ ];
23
+ const dlqFixtures = [
24
+ {
25
+ id: 'task-dlq-1',
26
+ title: 'Nightly sync',
27
+ status: 'failed',
28
+ message: 'Remote API timeout',
29
+ createdAt: '2026-04-18T23:00:00.000Z',
30
+ updatedAt: '2026-04-18T23:05:00.000Z',
31
+ error: { message: 'Remote API timeout' },
32
+ },
33
+ ];
34
+ const mockTaskProvider = {
35
+ submit: vi.fn(async () => ({
36
+ id: 'submitted-image.generate',
37
+ wait: async () => ({
38
+ id: 'submitted-image.generate',
39
+ title: 'image.generate',
40
+ status: 'queued',
41
+ createdAt: '2026-04-19T11:00:00.000Z',
42
+ updatedAt: '2026-04-19T11:00:00.000Z',
43
+ }),
44
+ })),
45
+ get: vi.fn(async (taskId) => {
46
+ return [...taskFixtures, ...dlqFixtures].find((task) => task.id === taskId) ?? taskFixtures[0];
47
+ }),
48
+ list: vi.fn(async () => ({ data: taskFixtures, total: taskFixtures.length })),
49
+ listDlq: vi.fn(async () => ({ data: dlqFixtures, total: dlqFixtures.length })),
50
+ retry: vi.fn(async () => undefined),
51
+ cancel: vi.fn(async () => undefined),
52
+ };
53
+ const refetchTasks = vi.fn(async () => ({ data: { data: taskFixtures, total: taskFixtures.length } }));
54
+ const refetchDlq = vi.fn(async () => ({ data: { data: dlqFixtures, total: dlqFixtures.length } }));
55
+ const mutateAsync = vi.fn(async (params) => ({
56
+ id: `submitted-${params.taskName}`,
57
+ wait: async () => ({
58
+ id: `submitted-${params.taskName}`,
59
+ title: params.taskName,
60
+ status: 'queued',
61
+ }),
62
+ }));
63
+ vi.mock('@svadmin/core', () => ({
64
+ getTaskProvider: () => mockTaskProvider,
65
+ useTaskList: ({ dlq }) => ({
66
+ data: dlq
67
+ ? { data: dlqFixtures, total: dlqFixtures.length }
68
+ : { data: taskFixtures, total: taskFixtures.length },
69
+ isLoading: false,
70
+ refetch: dlq ? refetchDlq : refetchTasks,
71
+ }),
72
+ useTask: ({ taskId }) => ({
73
+ data: [...taskFixtures, ...dlqFixtures].find((task) => task.id === taskId) ?? taskFixtures[0],
74
+ isLoading: false,
75
+ }),
76
+ useSubmitTask: () => ({
77
+ mutation: {
78
+ mutateAsync,
79
+ isPending: false,
80
+ },
81
+ }),
82
+ }));
83
+ vi.mock('@tanstack/svelte-query', () => ({
84
+ useQueryClient: () => ({
85
+ invalidateQueries: vi.fn(async () => undefined),
86
+ }),
87
+ }));
88
+ describe('TaskQueueDrawer', () => {
89
+ beforeEach(() => {
90
+ refetchTasks.mockClear();
91
+ refetchDlq.mockClear();
92
+ mutateAsync.mockClear();
93
+ mockTaskProvider.submit.mockClear();
94
+ mockTaskProvider.get.mockClear();
95
+ mockTaskProvider.list.mockClear();
96
+ mockTaskProvider.listDlq.mockClear();
97
+ mockTaskProvider.retry.mockClear();
98
+ mockTaskProvider.cancel.mockClear();
99
+ });
100
+ it('supports tab switching, filtering, and detail selection', async () => {
101
+ render(TaskQueueDrawer, { open: true, taskProvider: mockTaskProvider });
102
+ expect(screen.getByText('Task Center')).toBeTruthy();
103
+ expect(screen.getAllByText('Generate poster').length).toBeGreaterThan(0);
104
+ expect(screen.getAllByText('Rendering hero artwork').length).toBeGreaterThan(0);
105
+ const searchInput = screen.getByPlaceholderText('Filter tasks by name, ID, status, or message...');
106
+ await fireEvent.input(searchInput, { target: { value: 'email' } });
107
+ await waitFor(() => {
108
+ expect(screen.queryAllByText('Generate poster').length).toBeLessThan(2);
109
+ });
110
+ expect(screen.getByText('Send email batch')).toBeTruthy();
111
+ await fireEvent.input(searchInput, { target: { value: '' } });
112
+ await fireEvent.click(screen.getByRole('tab', { name: /DLQ/i }));
113
+ expect(screen.getByText('Nightly sync')).toBeTruthy();
114
+ const dlqRow = screen.getByText('Nightly sync').closest('tr');
115
+ expect(dlqRow).toBeTruthy();
116
+ await fireEvent.click(dlqRow);
117
+ await waitFor(() => {
118
+ expect(screen.getAllByText('Remote API timeout').length).toBeGreaterThan(0);
119
+ });
120
+ });
121
+ it('submits a task from the inline form', async () => {
122
+ render(TaskQueueDrawer, { open: true, taskProvider: mockTaskProvider });
123
+ await fireEvent.click(screen.getAllByRole('button', { name: 'Submit Task' })[0]);
124
+ await fireEvent.input(screen.getByLabelText('Task name'), {
125
+ target: { value: 'image.generate' },
126
+ });
127
+ await fireEvent.input(screen.getByLabelText('Task payload (JSON)'), {
128
+ target: { value: '{"prompt":"poster"}' },
129
+ });
130
+ const submitButtons = screen.getAllByRole('button', { name: 'Submit Task' });
131
+ await fireEvent.click(submitButtons[submitButtons.length - 1]);
132
+ await waitFor(() => {
133
+ expect(mutateAsync).toHaveBeenCalledWith({
134
+ taskName: 'image.generate',
135
+ taskProvider: mockTaskProvider,
136
+ options: {
137
+ body: { prompt: 'poster' },
138
+ idempotencyKey: undefined,
139
+ },
140
+ });
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import { Clock3, Loader2, CheckCircle2, AlertTriangle, CircleDashed } from '@lucide/svelte';
4
+ import { Badge } from './ui/badge/index.js';
5
+
6
+ let {
7
+ status = 'pending',
8
+ showIcon = true,
9
+ class: className = '',
10
+ } = $props<{
11
+ status?: string;
12
+ showIcon?: boolean;
13
+ class?: string;
14
+ }>();
15
+
16
+ const normalizedStatus = $derived(String(status || 'pending').toLowerCase());
17
+
18
+ function translateStatus(value: string) {
19
+ const key = `task.status.${value}`;
20
+ const translated = t(key);
21
+ if (translated !== key) return translated;
22
+ return value.charAt(0).toUpperCase() + value.slice(1);
23
+ }
24
+
25
+ const config = $derived.by(() => {
26
+ switch (normalizedStatus) {
27
+ case 'running':
28
+ case 'processing':
29
+ return {
30
+ label: translateStatus(normalizedStatus),
31
+ variant: 'default' as const,
32
+ icon: Loader2,
33
+ iconClass: 'animate-spin',
34
+ };
35
+ case 'completed':
36
+ case 'success':
37
+ case 'done':
38
+ return {
39
+ label: translateStatus(normalizedStatus),
40
+ variant: 'secondary' as const,
41
+ icon: CheckCircle2,
42
+ iconClass: 'text-green-600 dark:text-green-400',
43
+ };
44
+ case 'failed':
45
+ case 'error':
46
+ return {
47
+ label: translateStatus(normalizedStatus),
48
+ variant: 'destructive' as const,
49
+ icon: AlertTriangle,
50
+ iconClass: '',
51
+ };
52
+ case 'queued':
53
+ return {
54
+ label: t('task.status.queued'),
55
+ variant: 'outline' as const,
56
+ icon: CircleDashed,
57
+ iconClass: '',
58
+ };
59
+ case 'pending':
60
+ default:
61
+ return {
62
+ label: translateStatus(normalizedStatus),
63
+ variant: 'outline' as const,
64
+ icon: Clock3,
65
+ iconClass: '',
66
+ };
67
+ }
68
+ });
69
+
70
+ const Icon = $derived(config.icon);
71
+ </script>
72
+
73
+ <Badge variant={config.variant} class={`gap-1.5 ${className}`.trim()}>
74
+ {#if showIcon}
75
+ <Icon class={`h-3.5 w-3.5 ${config.iconClass}`.trim()} />
76
+ {/if}
77
+ <span>{config.label}</span>
78
+ </Badge>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ status?: string;
3
+ showIcon?: boolean;
4
+ class?: string;
5
+ };
6
+ declare const TaskStatusBadge: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type TaskStatusBadge = ReturnType<typeof TaskStatusBadge>;
8
+ export default TaskStatusBadge;
package/dist/index.d.ts CHANGED
@@ -66,7 +66,12 @@ export { default as AuditLogViewer } from "./components/AuditLogViewer.svelte";
66
66
  export { default as TenantSwitcher } from "./components/TenantSwitcher.svelte";
67
67
  export type { Tenant } from "./types.js";
68
68
  export { default as TaskQueueDrawer } from "./components/TaskQueueDrawer.svelte";
69
- export type { BackgroundTask, TaskStatus } from "./types.js";
69
+ export { default as TaskStatusBadge } from "./components/TaskStatusBadge.svelte";
70
+ export { default as TaskProgressBar } from "./components/TaskProgressBar.svelte";
71
+ export { default as TaskList } from "./components/TaskList.svelte";
72
+ export { default as TaskDetails } from "./components/TaskDetails.svelte";
73
+ export { default as RetryTaskButton } from "./components/RetryTaskButton.svelte";
74
+ export { default as CancelTaskButton } from "./components/CancelTaskButton.svelte";
70
75
  export { default as DraggableGrid } from "./components/DraggableGrid.svelte";
71
76
  export type { GridModule } from "./types.js";
72
77
  export { BarChart, LineChart, PieChart } from './components/charts/index.js';
package/dist/index.js CHANGED
@@ -69,6 +69,12 @@ export { default as RolesSettings } from "./components/RolesSettings.svelte";
69
69
  export { default as AuditLogViewer } from "./components/AuditLogViewer.svelte";
70
70
  export { default as TenantSwitcher } from "./components/TenantSwitcher.svelte";
71
71
  export { default as TaskQueueDrawer } from "./components/TaskQueueDrawer.svelte";
72
+ export { default as TaskStatusBadge } from "./components/TaskStatusBadge.svelte";
73
+ export { default as TaskProgressBar } from "./components/TaskProgressBar.svelte";
74
+ export { default as TaskList } from "./components/TaskList.svelte";
75
+ export { default as TaskDetails } from "./components/TaskDetails.svelte";
76
+ export { default as RetryTaskButton } from "./components/RetryTaskButton.svelte";
77
+ export { default as CancelTaskButton } from "./components/CancelTaskButton.svelte";
72
78
  export { default as DraggableGrid } from "./components/DraggableGrid.svelte";
73
79
  // Dashboard Charts (zero-dependency SVG)
74
80
  export { BarChart, LineChart, PieChart } from './components/charts/index.js';
package/dist/types.d.ts CHANGED
@@ -20,17 +20,6 @@ export interface Tenant {
20
20
  logo?: string;
21
21
  [key: string]: unknown;
22
22
  }
23
- export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed';
24
- export interface BackgroundTask {
25
- id: string;
26
- title: string;
27
- status: TaskStatus;
28
- progress?: number;
29
- message?: string;
30
- createdAt: string | Date;
31
- downloadUrl?: string;
32
- [key: string]: unknown;
33
- }
34
23
  export interface GridModule {
35
24
  id: string;
36
25
  w: number;
package/package.json CHANGED
@@ -1,33 +1,32 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.33.1",
3
+ "version": "0.34.1",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
7
- "./src/app.css"
7
+ "./dist/app.css"
8
8
  ],
9
9
  "files": [
10
- "dist",
11
- "src/app.css"
10
+ "dist"
12
11
  ],
13
12
  "main": "dist/index.js",
14
13
  "types": "dist/index.d.ts",
15
14
  "exports": {
16
15
  ".": {
17
16
  "types": "./dist/index.d.ts",
18
- "svelte": "./src/index.ts",
17
+ "svelte": "./dist/index.js",
19
18
  "default": "./dist/index.js"
20
19
  },
21
20
  "./app.css": {
22
- "svelte": "./src/app.css",
23
- "import": "./src/app.css",
24
- "style": "./src/app.css",
25
- "default": "./src/app.css"
21
+ "svelte": "./dist/app.css",
22
+ "import": "./dist/app.css",
23
+ "style": "./dist/app.css",
24
+ "default": "./dist/app.css"
26
25
  },
27
26
  "./components/*": "./dist/components/*"
28
27
  },
29
28
  "peerDependencies": {
30
- "@svadmin/core": "^0.24.0",
29
+ "@svadmin/core": "^0.25.0",
31
30
  "@tanstack/svelte-query": "^6.0.0",
32
31
  "highlight.js": "^11.11.1",
33
32
  "isomorphic-dompurify": "^3.7.1",
@@ -63,7 +62,7 @@
63
62
  },
64
63
  "dependencies": {
65
64
  "@lucide/svelte": "^1.7.0",
66
- "@tanstack/svelte-table": "^9.0.0-alpha.32",
65
+ "@tanstack/svelte-table": "9.0.0-alpha.32",
67
66
  "bits-ui": "^2.17.1",
68
67
  "clsx": "^2.1.1",
69
68
  "cmdk-sv": "^0.0.19",
@@ -82,8 +81,17 @@
82
81
  "bugs": {
83
82
  "url": "https://github.com/zuohuadong/svadmin/issues"
84
83
  },
85
- "devDependencies": {},
84
+ "devDependencies": {
85
+ "@happy-dom/global-registrator": "^20.9.0",
86
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
87
+ "@testing-library/dom": "^10.4.1",
88
+ "@testing-library/svelte": "^5.3.1",
89
+ "@testing-library/user-event": "^14.6.1",
90
+ "happy-dom": "^20.9.0",
91
+ "vitest": "^4.1.4"
92
+ },
86
93
  "scripts": {
87
- "build": "svelte-package -i src -o dist"
94
+ "build": "svelte-package -i src -o dist",
95
+ "test": "vitest run --config ./vitest.config.ts"
88
96
  }
89
97
  }