@svadmin/core 0.25.1 → 0.25.3

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.
@@ -1,79 +0,0 @@
1
- // Resource Type Registry — type-level tests
2
- import { describe, test, expect } from 'bun:test';
3
- import type { ResourceTypeMap, KnownResources, InferData } from './types';
4
-
5
- describe('Resource Type Registry', () => {
6
- test('ResourceTypeMap is an empty interface by default', () => {
7
- // When no declaration merging is used, keyof ResourceTypeMap = never
8
- type Keys = keyof ResourceTypeMap;
9
- // This compiles — verifying the interface exists and is empty
10
- const _check: Keys extends never ? true : false = true;
11
- expect(_check).toBe(true);
12
- });
13
-
14
- test('KnownResources falls back to string when ResourceTypeMap is empty', () => {
15
- // When ResourceTypeMap is empty, KnownResources should be string
16
- const resource: KnownResources = 'anything';
17
- expect(typeof resource).toBe('string');
18
- });
19
-
20
- test('InferData falls back to Record<string, unknown> for unknown resources', () => {
21
- // For any resource not in ResourceTypeMap, InferData should return Record<string, unknown>
22
- type Result = InferData<'nonexistent'>;
23
- const data: Result = { arbitrary: 'value', number: 42 };
24
- expect(data.arbitrary).toBe('value');
25
- });
26
-
27
- test('type utilities are structurally sound', () => {
28
- // Simulate what happens with declaration merging by testing the conditional types
29
- // When R extends keyof ResourceTypeMap (which is never when empty),
30
- // InferData should return Record<string, unknown>
31
- type TestInfer = InferData<'users'>;
32
- const obj: TestInfer = { id: '1', name: 'test' };
33
- expect(obj).toBeDefined();
34
- });
35
-
36
- test('KnownResources accepts any string when ResourceTypeMap is empty', () => {
37
- // This verifies backward compatibility — all strings are valid resource names
38
- const resources: KnownResources[] = ['users', 'posts', 'categories', 'anything'];
39
- expect(resources).toHaveLength(4);
40
- });
41
- });
42
-
43
- // ─── Declaration Merging Example (compile-time only) ──────────
44
- // The following block verifies that declaration merging works correctly.
45
- // It's wrapped in a function so it doesn't affect runtime.
46
-
47
- function _typeTests() {
48
- // Simulate declaration merging
49
- interface TestResourceMap {
50
- users: { id: string; name: string; email: string };
51
- posts: { id: string; title: string; content: string };
52
- }
53
-
54
- // Test conditional type behavior with a populated map
55
- type TestKnown<M> = keyof M extends never ? string : Extract<keyof M, string>;
56
- type TestInfer<R extends string, M> = R extends keyof M ? M[R] : Record<string, unknown>;
57
-
58
- // When map is populated, resources are constrained
59
- type Resources = TestKnown<TestResourceMap>;
60
- const _r1: Resources = 'users';
61
- const _r2: Resources = 'posts';
62
- // @ts-expect-error — 'invalid' is not in TestResourceMap
63
- const _r3: Resources = 'invalid';
64
-
65
- // InferData resolves correctly
66
- type UserData = TestInfer<'users', TestResourceMap>;
67
- const _user: UserData = { id: '1', name: 'Alice', email: 'a@b.com' };
68
-
69
- type PostData = TestInfer<'posts', TestResourceMap>;
70
- const _post: PostData = { id: '1', title: 'Hello', content: 'World' };
71
-
72
- // Unknown resource falls back
73
- type UnknownData = TestInfer<'unknown', TestResourceMap>;
74
- const _unknown: UnknownData = { any: 'thing' };
75
-
76
- void _r1; void _r2; void _r3; void _user; void _post; void _unknown;
77
- }
78
-
79
- void _typeTests;
@@ -1,82 +0,0 @@
1
- // Unit tests for RouterProvider implementations
2
- // Router provider tests need a DOM environment or a mock.
3
- import { describe, test, expect, beforeEach } from 'bun:test';
4
- import { createHashRouterProvider, createHistoryRouterProvider } from './router-provider';
5
-
6
- describe('createHashRouterProvider (unit — no DOM)', () => {
7
-
8
- test('returns provider with go, back, parse methods', () => {
9
- const provider = createHashRouterProvider();
10
- expect(typeof provider.go).toBe('function');
11
- expect(typeof provider.back).toBe('function');
12
- expect(typeof provider.parse).toBe('function');
13
- });
14
- });
15
-
16
- describe('createHistoryRouterProvider (unit — no DOM)', () => {
17
- test('returns provider with go, back, parse methods', () => {
18
- const provider = createHistoryRouterProvider();
19
- expect(typeof provider.go).toBe('function');
20
- expect(typeof provider.back).toBe('function');
21
- expect(typeof provider.parse).toBe('function');
22
- });
23
-
24
- test('accepts basePath argument', () => {
25
- const provider = createHistoryRouterProvider('/admin');
26
- expect(typeof provider.parse).toBe('function');
27
- });
28
- });
29
-
30
- // DOM-dependent tests — mocked window if not exists
31
- describe('createHashRouterProvider (with mock DOM)', () => {
32
- beforeEach(() => {
33
- if (typeof globalThis.window === 'undefined') {
34
- let _hash = '';
35
- (globalThis as any).window = {
36
- location: {
37
- get hash() { return _hash ? (_hash.startsWith('#') ? _hash : '#' + _hash) : ''; },
38
- set hash(val) { _hash = val; }
39
- }
40
- };
41
- }
42
- });
43
-
44
- test('parse returns empty segments for root', () => {
45
- window.location.hash = '';
46
- const provider = createHashRouterProvider();
47
- const parsed = provider.parse();
48
- expect(parsed.pathname).toBe('/');
49
- });
50
-
51
- test('parse extracts resource from hash', () => {
52
- window.location.hash = '#/posts';
53
- const provider = createHashRouterProvider();
54
- const parsed = provider.parse();
55
- expect(parsed.pathname).toBe('/posts');
56
- expect(parsed.resource).toBe('posts');
57
- });
58
-
59
- test('parse extracts resource, action, and id', () => {
60
- window.location.hash = '#/posts/edit/42';
61
- const provider = createHashRouterProvider();
62
- const parsed = provider.parse();
63
- expect(parsed.resource).toBe('posts');
64
- expect(parsed.action).toBe('edit');
65
- expect(parsed.id).toBe('42');
66
- });
67
-
68
- test('parse extracts query params from hash', () => {
69
- window.location.hash = '#/posts?page=2&sort=name';
70
- const provider = createHashRouterProvider();
71
- const parsed = provider.parse();
72
- expect(parsed.params.page).toBe('2');
73
- expect(parsed.params.sort).toBe('name');
74
- });
75
-
76
- test('go sets window.location.hash', () => {
77
- const provider = createHashRouterProvider();
78
- provider.go({ to: '/users' });
79
- expect(window.location.hash).toBe('#/users');
80
- });
81
- });
82
-
@@ -1,55 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { useGo, useBack } from './routing-hooks.svelte';
3
- import { useParsed } from './useParsed.svelte';
4
- import { flushSync } from 'svelte';
5
-
6
- vi.mock('./context.svelte', () => ({
7
- useRouterContext: () => ({
8
- navigate: vi.fn(),
9
- url: '/posts/1/edit',
10
- parsedParams: { resource: 'posts', action: 'edit', id: '1' },
11
- go: vi.fn(),
12
- back: vi.fn()
13
- }),
14
- getRouterProvider: () => undefined
15
- }));
16
-
17
- vi.mock('./useParsed.svelte', () => ({
18
- useParsed: () => ({ resource: 'posts', action: 'edit', id: '1' })
19
- }));
20
-
21
- describe('routing-hooks - Headless Svelte 5 Compatibility', () => {
22
-
23
- it('safely extracts parses from url and binds to runes Context', () => {
24
- let parsedParams!: ReturnType<typeof useParsed>;
25
-
26
- const cleanup = $effect.root(() => {
27
- parsedParams = useParsed();
28
- });
29
-
30
- flushSync();
31
-
32
- expect(parsedParams!.action).toBe('edit');
33
- expect(parsedParams.resource).toBe('posts');
34
- expect(parsedParams.id).toBe('1');
35
-
36
- cleanup();
37
- });
38
-
39
- it('useGo and useBack return functional closures out of Context', () => {
40
- let go!: ReturnType<typeof useGo>;
41
- let back!: ReturnType<typeof useBack>;
42
-
43
- const cleanup = $effect.root(() => {
44
- go = useGo();
45
- back = useBack();
46
- });
47
-
48
- flushSync();
49
-
50
- expect(typeof go).toBe('function');
51
- expect(typeof back).toBe('function');
52
-
53
- cleanup();
54
- });
55
- });
@@ -1,57 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { useTable } from './table-hooks.svelte';
3
- import { flushSync } from 'svelte';
4
- import { QueryClient } from '@tanstack/svelte-query';
5
-
6
- vi.mock('./context.svelte', () => ({
7
- useDataProvider: () => ({
8
- getList: vi.fn().mockResolvedValue({ data: [], total: 0 })
9
- }),
10
- useResource: () => ({ name: 'posts' }),
11
- getResource: (name: string) => ({ name, primaryKey: 'id' }),
12
- useRouterContext: () => ({ navigate: vi.fn(), url: '/', parsedParams: { current: 1, pageSize: 10 } }),
13
- useTranslate: () => (key: string) => key,
14
- getSyncWithLocation: () => false,
15
- useNotification: () => ({ open: vi.fn(), close: vi.fn() }),
16
- getLiveProvider: () => undefined
17
- }));
18
-
19
- vi.mock('@tanstack/svelte-query', async (importOriginal) => {
20
- const actual = await importOriginal();
21
- return {
22
- ...actual as any,
23
- useQueryClient: () => new QueryClient(),
24
- createQuery: () => ({
25
- data: { data: [{ id: 1, title: 'Row' }], total: 1 },
26
- isPending: false,
27
- isFetching: false,
28
- error: null
29
- })
30
- };
31
- });
32
-
33
- describe('useTable - Headless Svelte 5 Compatibility', () => {
34
-
35
- it('binds useTable logic internally and updates pagination flawlessly via $state', () => {
36
- let table: ReturnType<typeof useTable>;
37
-
38
- const cleanup = $effect.root(() => {
39
- table = useTable({ resource: 'posts' });
40
- });
41
-
42
- flushSync();
43
-
44
- expect(table!.query).toBeDefined();
45
- expect(table!.query?.data?.data[0].title).toBe('Row');
46
- expect(table!.current).toBe(1);
47
-
48
- // Trigger mutable state
49
- table!.setPage(2);
50
-
51
- flushSync();
52
-
53
- expect(table!.current).toBe(2);
54
-
55
- cleanup();
56
- });
57
- });
@@ -1,129 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { flushSync } from 'svelte';
3
- import { QueryClient } from '@tanstack/svelte-query';
4
- import { useSubmitTask, useTask, useTaskList, useTaskSubscription } from './task-hooks.svelte';
5
-
6
- const mockTaskProvider = {
7
- submit: vi.fn(async () => ({
8
- id: 'task-1',
9
- wait: async () => ({ id: 'task-1', status: 'done' }),
10
- })),
11
- get: vi.fn(async (taskId: string) => ({ id: taskId, status: 'running' })),
12
- list: vi.fn(async () => ({ data: [{ id: 'task-1', status: 'queued' }], total: 1 })),
13
- listDlq: vi.fn(async () => ({ data: [{ id: 'task-2', status: 'failed' }], total: 1 })),
14
- subscribe: vi.fn((_taskId: string, callback: (task: { id: string; status: string }) => void) => {
15
- callback({ id: 'task-1', status: 'running' });
16
- return () => {};
17
- }),
18
- };
19
-
20
- vi.mock('./context.svelte', () => ({
21
- getTaskProvider: () => mockTaskProvider,
22
- getAuthProvider: () => null,
23
- }));
24
-
25
- vi.mock('./notification.svelte', () => ({
26
- notify: vi.fn(),
27
- }));
28
-
29
- vi.mock('@tanstack/svelte-query', async (importOriginal) => {
30
- const actual = await importOriginal();
31
- return {
32
- ...actual as any,
33
- useQueryClient: () => new QueryClient(),
34
- createMutation: (factory: any) => {
35
- const config = factory();
36
- return {
37
- mutateAsync: async (vars: unknown) => config.mutationFn(vars),
38
- mutate: vi.fn(),
39
- isPending: false,
40
- };
41
- },
42
- createQuery: (factory: any) => {
43
- const config = factory();
44
- return {
45
- data: config.queryKey[0] === 'taskList'
46
- ? { data: [{ id: 'task-1', status: 'queued' }], total: 1 }
47
- : { id: 'task-1', status: 'running' },
48
- isLoading: false,
49
- isSuccess: true,
50
- isError: false,
51
- error: null,
52
- dataUpdatedAt: Date.now(),
53
- errorUpdatedAt: 0,
54
- };
55
- },
56
- };
57
- });
58
-
59
- describe('task-hooks', () => {
60
- it('useSubmitTask exposes a working mutation', async () => {
61
- let submitTask: ReturnType<typeof useSubmitTask>;
62
-
63
- const cleanup = $effect.root(() => {
64
- submitTask = useSubmitTask();
65
- });
66
-
67
- flushSync();
68
-
69
- const handle = await submitTask!.mutation.mutateAsync({
70
- taskName: 'image.generate',
71
- options: { body: { prompt: 'poster' } },
72
- });
73
-
74
- expect(handle.id).toBe('task-1');
75
- expect(mockTaskProvider.submit).toHaveBeenCalledWith('image.generate', {
76
- body: { prompt: 'poster' },
77
- });
78
-
79
- cleanup();
80
- });
81
-
82
- it('useTask returns query state for a single task', () => {
83
- let taskQuery: ReturnType<typeof useTask>;
84
-
85
- const cleanup = $effect.root(() => {
86
- taskQuery = useTask({ taskId: 'task-1' });
87
- });
88
-
89
- flushSync();
90
-
91
- expect(taskQuery!.data).toBeDefined();
92
- expect((taskQuery!.data as any).status).toBe('running');
93
-
94
- cleanup();
95
- });
96
-
97
- it('useTaskList returns task collection data', () => {
98
- let taskListQuery: ReturnType<typeof useTaskList>;
99
-
100
- const cleanup = $effect.root(() => {
101
- taskListQuery = useTaskList();
102
- });
103
-
104
- flushSync();
105
-
106
- expect(taskListQuery!.data).toBeDefined();
107
- expect((taskListQuery!.data as any).data[0].id).toBe('task-1');
108
-
109
- cleanup();
110
- });
111
-
112
- it('useTaskSubscription subscribes to task updates', () => {
113
- const onTask = vi.fn();
114
-
115
- const cleanup = $effect.root(() => {
116
- useTaskSubscription({
117
- taskId: 'task-1',
118
- onTask,
119
- });
120
- });
121
-
122
- flushSync();
123
-
124
- expect(mockTaskProvider.subscribe).toHaveBeenCalledWith('task-1', expect.any(Function));
125
- expect(onTask).toHaveBeenCalledWith({ id: 'task-1', status: 'running' });
126
-
127
- cleanup();
128
- });
129
- });