@tenxyte/core 0.1.5 → 0.9.2
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.
- package/README.md +444 -0
- package/dist/index.cjs +1881 -496
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2767 -1265
- package/dist/index.d.ts +2767 -1265
- package/dist/index.js +1830 -464
- package/dist/index.js.map +1 -1
- package/package.json +83 -67
- package/patched-schema.json +0 -11388
- package/src/client.ts +0 -21
- package/src/config.ts +0 -0
- package/src/http/client.ts +0 -162
- package/src/http/index.ts +0 -1
- package/src/http/interceptors.ts +0 -117
- package/src/index.ts +0 -7
- package/src/modules/ai.ts +0 -0
- package/src/modules/auth.ts +0 -95
- package/src/modules/b2b.ts +0 -0
- package/src/modules/rbac.ts +0 -160
- package/src/modules/security.ts +0 -122
- package/src/modules/user.ts +0 -80
- package/src/storage/cookie.ts +0 -39
- package/src/storage/index.ts +0 -29
- package/src/storage/localStorage.ts +0 -75
- package/src/storage/memory.ts +0 -30
- package/src/types/api-schema.d.ts +0 -6590
- package/src/types/index.ts +0 -150
- package/src/utils/device_info.ts +0 -94
- package/src/utils/events.ts +0 -71
- package/src/utils/jwt.ts +0 -51
- package/tests/http.test.ts +0 -144
- package/tests/modules/auth.test.ts +0 -93
- package/tests/modules/rbac.test.ts +0 -95
- package/tests/modules/security.test.ts +0 -75
- package/tests/modules/user.test.ts +0 -76
- package/tests/storage.test.ts +0 -96
- package/tests/utils.test.ts +0 -71
- package/tsconfig.json +0 -26
- package/tsup.config.ts +0 -10
- package/vitest.config.ts +0 -7
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { RbacModule } from '../../src/modules/rbac';
|
|
3
|
-
import { TenxyteHttpClient } from '../../src/http/client';
|
|
4
|
-
|
|
5
|
-
// Helper to create a dummy JWT for testing
|
|
6
|
-
function createDummyJwt(payload: any) {
|
|
7
|
-
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
8
|
-
return `header.${encodedPayload}.signature`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('RbacModule', () => {
|
|
12
|
-
let client: TenxyteHttpClient;
|
|
13
|
-
let rbac: RbacModule;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
|
|
17
|
-
rbac = new RbacModule(client);
|
|
18
|
-
vi.spyOn(client, 'request').mockImplementation(async () => {
|
|
19
|
-
return {};
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('Synchronous Decoding & Checks', () => {
|
|
24
|
-
const token = createDummyJwt({
|
|
25
|
-
roles: ['admin', 'manager'],
|
|
26
|
-
permissions: ['users.view', 'users.edit']
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('hasRole should return true if role exists', () => {
|
|
30
|
-
expect(rbac.hasRole('admin', token)).toBe(true);
|
|
31
|
-
expect(rbac.hasRole('user', token)).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('hasAnyRole should return true if any role exists', () => {
|
|
35
|
-
expect(rbac.hasAnyRole(['admin', 'user'], token)).toBe(true);
|
|
36
|
-
expect(rbac.hasAnyRole(['user', 'guest'], token)).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('hasAllRoles should return true if all roles exist', () => {
|
|
40
|
-
expect(rbac.hasAllRoles(['admin', 'manager'], token)).toBe(true);
|
|
41
|
-
expect(rbac.hasAllRoles(['admin', 'user'], token)).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('hasPermission should return true if permission exists', () => {
|
|
45
|
-
expect(rbac.hasPermission('users.view', token)).toBe(true);
|
|
46
|
-
expect(rbac.hasPermission('users.delete', token)).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('setToken should cache the token for parameter-less calls', () => {
|
|
50
|
-
rbac.setToken(token);
|
|
51
|
-
expect(rbac.hasRole('admin')).toBe(true);
|
|
52
|
-
expect(rbac.hasPermission('users.edit')).toBe(true);
|
|
53
|
-
|
|
54
|
-
rbac.setToken(null);
|
|
55
|
-
expect(rbac.hasRole('admin')).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('Roles CRUD & Permissions', () => {
|
|
60
|
-
it('listRoles should GET /api/v1/auth/roles/', async () => {
|
|
61
|
-
vi.mocked(client.request).mockResolvedValueOnce([{ id: '1', name: 'admin' }]);
|
|
62
|
-
const result = await rbac.listRoles();
|
|
63
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/', { method: 'GET' });
|
|
64
|
-
expect(result.length).toBe(1);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('createRole should POST /api/v1/auth/roles/', async () => {
|
|
68
|
-
vi.mocked(client.request).mockResolvedValueOnce({ id: '2', name: 'newRole' });
|
|
69
|
-
const data = { name: 'newRole', permission_codes: ['a.b'] };
|
|
70
|
-
await rbac.createRole(data);
|
|
71
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/', {
|
|
72
|
-
method: 'POST',
|
|
73
|
-
body: data,
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('assignRoleToUser should POST to users/{id}/roles/', async () => {
|
|
78
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
79
|
-
await rbac.assignRoleToUser('user123', 'admin');
|
|
80
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/users/user123/roles/', {
|
|
81
|
-
method: 'POST',
|
|
82
|
-
body: { role_code: 'admin' },
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('removePermissionsFromRole should DELETE with body payload if config allows', async () => {
|
|
87
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
88
|
-
await rbac.removePermissionsFromRole('role123', ['users.view']);
|
|
89
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/roles/role123/permissions/', {
|
|
90
|
-
method: 'DELETE',
|
|
91
|
-
body: { permission_codes: ['users.view'] },
|
|
92
|
-
} as any);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { SecurityModule } from '../../src/modules/security';
|
|
3
|
-
import { TenxyteHttpClient } from '../../src/http/client';
|
|
4
|
-
|
|
5
|
-
describe('SecurityModule', () => {
|
|
6
|
-
let client: TenxyteHttpClient;
|
|
7
|
-
let security: SecurityModule;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
|
|
11
|
-
security = new SecurityModule(client);
|
|
12
|
-
vi.spyOn(client, 'request').mockImplementation(async () => {
|
|
13
|
-
return {};
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('requestOtp should POST to /api/v1/auth/otp/request/', async () => {
|
|
18
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
19
|
-
const data = { email: 'test@example.com', type: 'email_verification' as const };
|
|
20
|
-
await security.requestOtp(data);
|
|
21
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/otp/request/', {
|
|
22
|
-
method: 'POST',
|
|
23
|
-
body: data,
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('setup2FA should POST to /api/v1/auth/2fa/setup/', async () => {
|
|
28
|
-
const mockResponse = { secret: 'secret_key', qr_code_url: 'url', backup_codes: ['123'] };
|
|
29
|
-
vi.mocked(client.request).mockResolvedValueOnce(mockResponse);
|
|
30
|
-
|
|
31
|
-
const result = await security.setup2FA();
|
|
32
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/2fa/setup/', {
|
|
33
|
-
method: 'POST',
|
|
34
|
-
body: undefined
|
|
35
|
-
});
|
|
36
|
-
expect(result).toEqual(mockResponse);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('confirm2FA should POST to /api/v1/auth/2fa/confirm/', async () => {
|
|
40
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
41
|
-
await security.confirm2FA('123456');
|
|
42
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/2fa/confirm/', {
|
|
43
|
-
method: 'POST',
|
|
44
|
-
body: { totp_code: '123456' },
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('resetPasswordRequest should POST to /api/v1/auth/password/reset/request/', async () => {
|
|
49
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
50
|
-
await security.resetPasswordRequest({ email: 'hello@example.com' });
|
|
51
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/password/reset/request/', {
|
|
52
|
-
method: 'POST',
|
|
53
|
-
body: { email: 'hello@example.com' },
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('authenticateWebAuthnBegin should POST to /api/v1/auth/webauthn/authenticate/begin/', async () => {
|
|
58
|
-
const mockResponse = { publicKey: {} };
|
|
59
|
-
vi.mocked(client.request).mockResolvedValueOnce(mockResponse);
|
|
60
|
-
const result = await security.authenticateWebAuthnBegin({ email: 'user@example.com' });
|
|
61
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/webauthn/authenticate/begin/', {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
body: { email: 'user@example.com' },
|
|
64
|
-
});
|
|
65
|
-
expect(result).toEqual(mockResponse);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('deleteWebAuthnCredential should DELETE /api/v1/auth/webauthn/credentials/{credentialId}/', async () => {
|
|
69
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
70
|
-
await security.deleteWebAuthnCredential('cred_123');
|
|
71
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/webauthn/credentials/cred_123/', {
|
|
72
|
-
method: 'DELETE',
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { UserModule } from '../../src/modules/user';
|
|
3
|
-
import { TenxyteHttpClient } from '../../src/http/client';
|
|
4
|
-
|
|
5
|
-
describe('UserModule', () => {
|
|
6
|
-
let client: TenxyteHttpClient;
|
|
7
|
-
let user: UserModule;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
client = new TenxyteHttpClient({ baseUrl: 'http://localhost:8000' });
|
|
11
|
-
user = new UserModule(client);
|
|
12
|
-
vi.spyOn(client, 'request').mockImplementation(async () => {
|
|
13
|
-
return {};
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// --- Profile ---
|
|
18
|
-
it('getProfile should GET /api/v1/auth/me/', async () => {
|
|
19
|
-
vi.mocked(client.request).mockResolvedValueOnce({ id: 'me' });
|
|
20
|
-
const result = await user.getProfile();
|
|
21
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', { method: 'GET' });
|
|
22
|
-
expect(result.id).toBe('me');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('updateProfile should PATCH /api/v1/auth/me/', async () => {
|
|
26
|
-
vi.mocked(client.request).mockResolvedValueOnce({ id: 'me' });
|
|
27
|
-
const data = { first_name: 'John' };
|
|
28
|
-
await user.updateProfile(data);
|
|
29
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', {
|
|
30
|
-
method: 'PATCH',
|
|
31
|
-
body: data,
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('uploadAvatar should PATCH /api/v1/auth/me/ with FormData', async () => {
|
|
36
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
37
|
-
|
|
38
|
-
// Mock global FormData if not in purely browser environment
|
|
39
|
-
const FormDataMock = class { append() { } };
|
|
40
|
-
const formData = new FormDataMock() as any;
|
|
41
|
-
|
|
42
|
-
await user.uploadAvatar(formData);
|
|
43
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/me/', {
|
|
44
|
-
method: 'PATCH',
|
|
45
|
-
body: formData,
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('deleteAccount should POST to /api/v1/auth/request-account-deletion/', async () => {
|
|
50
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
51
|
-
await user.deleteAccount('mypassword', '123456');
|
|
52
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/request-account-deletion/', {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
body: { password: 'mypassword', otp_code: '123456' },
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// --- Admin Actions ---
|
|
59
|
-
it('listUsers should GET /api/v1/auth/admin/users/', async () => {
|
|
60
|
-
vi.mocked(client.request).mockResolvedValueOnce([]);
|
|
61
|
-
await user.listUsers({ page: 1 });
|
|
62
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/admin/users/', {
|
|
63
|
-
method: 'GET',
|
|
64
|
-
params: { page: 1 },
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('banUser should POST /api/v1/auth/admin/users/{id}/ban/', async () => {
|
|
69
|
-
vi.mocked(client.request).mockResolvedValueOnce(undefined);
|
|
70
|
-
await user.banUser('user-1', 'spam');
|
|
71
|
-
expect(client.request).toHaveBeenCalledWith('/api/v1/auth/admin/users/user-1/ban/', {
|
|
72
|
-
method: 'POST',
|
|
73
|
-
body: { reason: 'spam' },
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
});
|
package/tests/storage.test.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { MemoryStorage, LocalStorage } from '../src/storage';
|
|
3
|
-
|
|
4
|
-
describe('Storage Abstractions', () => {
|
|
5
|
-
describe('MemoryStorage', () => {
|
|
6
|
-
let storage: MemoryStorage;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
storage = new MemoryStorage();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should set and get items', () => {
|
|
13
|
-
storage.setItem('key1', 'value1');
|
|
14
|
-
expect(storage.getItem('key1')).toBe('value1');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should return null for non-existent keys', () => {
|
|
18
|
-
expect(storage.getItem('key2')).toBeNull();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should remove items', () => {
|
|
22
|
-
storage.setItem('key1', 'value1');
|
|
23
|
-
storage.removeItem('key1');
|
|
24
|
-
expect(storage.getItem('key1')).toBeNull();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should clear all items', () => {
|
|
28
|
-
storage.setItem('key1', 'value1');
|
|
29
|
-
storage.setItem('key2', 'value2');
|
|
30
|
-
storage.clear();
|
|
31
|
-
expect(storage.getItem('key1')).toBeNull();
|
|
32
|
-
expect(storage.getItem('key2')).toBeNull();
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('LocalStorage', () => {
|
|
37
|
-
let storage: LocalStorage;
|
|
38
|
-
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
// Mock window.localStorage for node environment
|
|
41
|
-
const localStorageMock = (() => {
|
|
42
|
-
let store: Record<string, string> = {};
|
|
43
|
-
return {
|
|
44
|
-
getItem: vi.fn((key: string) => store[key] || null),
|
|
45
|
-
setItem: vi.fn((key: string, value: string) => {
|
|
46
|
-
if (key === 'fallback_key') throw new Error('Quota'); // For fallback test
|
|
47
|
-
store[key] = value.toString();
|
|
48
|
-
}),
|
|
49
|
-
removeItem: vi.fn((key: string) => {
|
|
50
|
-
delete store[key];
|
|
51
|
-
}),
|
|
52
|
-
clear: vi.fn(() => {
|
|
53
|
-
store = {};
|
|
54
|
-
}),
|
|
55
|
-
};
|
|
56
|
-
})();
|
|
57
|
-
|
|
58
|
-
Object.defineProperty(window, 'localStorage', {
|
|
59
|
-
value: localStorageMock,
|
|
60
|
-
writable: true,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
storage = new LocalStorage();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should set and get items in browser localStorage', () => {
|
|
67
|
-
storage.setItem('tx_test', 'value123');
|
|
68
|
-
expect(storage.getItem('tx_test')).toBe('value123');
|
|
69
|
-
expect(localStorage.getItem('tx_test')).toBe('value123');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should return null for non-existent keys', () => {
|
|
73
|
-
expect(storage.getItem('missing')).toBeNull();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should degrade to MemoryStorage if window.localStorage throws error', () => {
|
|
77
|
-
// The mock throws an error for 'fallback_key' specifically, but 'new LocalStorage()' checking availability passes.
|
|
78
|
-
// Wait, checkAvailability() tries to set '__tenxyte_test__'.
|
|
79
|
-
// To test constructor degradation, we can mock localStorage globally to throw on everything during setup:
|
|
80
|
-
Object.defineProperty(window, 'localStorage', {
|
|
81
|
-
value: {
|
|
82
|
-
setItem: () => { throw new Error('QuotaExceededError'); },
|
|
83
|
-
getItem: () => null,
|
|
84
|
-
removeItem: () => { },
|
|
85
|
-
clear: () => { }
|
|
86
|
-
},
|
|
87
|
-
writable: true,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const strictStorage = new LocalStorage();
|
|
91
|
-
|
|
92
|
-
strictStorage.setItem('fallback_key', 'test');
|
|
93
|
-
expect(strictStorage.getItem('fallback_key')).toBe('test');
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
package/tests/utils.test.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { buildDeviceInfo } from '../src/utils/device_info';
|
|
3
|
-
import { EventEmitter } from '../src/utils/events';
|
|
4
|
-
|
|
5
|
-
describe('Utilities', () => {
|
|
6
|
-
describe('buildDeviceInfo', () => {
|
|
7
|
-
it('should respect custom properties', () => {
|
|
8
|
-
const info = buildDeviceInfo({
|
|
9
|
-
os: 'macos',
|
|
10
|
-
osVersion: '14.0',
|
|
11
|
-
device: 'desktop',
|
|
12
|
-
arch: 'arm64',
|
|
13
|
-
app: 'tenxyte-test',
|
|
14
|
-
appVersion: '2.0.0',
|
|
15
|
-
runtime: 'node',
|
|
16
|
-
runtimeVersion: 'v20.0.0',
|
|
17
|
-
timezone: 'America/New_York'
|
|
18
|
-
});
|
|
19
|
-
// v=1|os=macos;osv=14.0|device=desktop|arch=arm64|app=tenxyte-test;appv=2.0.0|runtime=node;rtv=v20.0.0|tz=America/New_York
|
|
20
|
-
expect(info).toContain('os=macos;osv=14.0');
|
|
21
|
-
expect(info).toContain('app=tenxyte-test;appv=2.0.0');
|
|
22
|
-
expect(info).toContain('runtime=node;rtv=v20.0.0');
|
|
23
|
-
expect(info).toContain('tz=America/New_York');
|
|
24
|
-
expect(info).toContain('v=1');
|
|
25
|
-
expect(info).toContain('device=desktop');
|
|
26
|
-
expect(info).toContain('arch=arm64');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should generate sensible defaults in absence of custom info', () => {
|
|
30
|
-
const info = buildDeviceInfo();
|
|
31
|
-
expect(info).toContain('v=1');
|
|
32
|
-
// Should have parsed process or window depending on env
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('EventEmitter', () => {
|
|
37
|
-
type TestEvents = {
|
|
38
|
-
'test:event': { data: string };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
let emitter: EventEmitter<TestEvents>;
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
emitter = new EventEmitter();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should allow emitting and receiving events', () => {
|
|
48
|
-
const mockCb = vi.fn();
|
|
49
|
-
emitter.on('test:event', mockCb);
|
|
50
|
-
emitter.emit('test:event', { data: 'hello' });
|
|
51
|
-
expect(mockCb).toHaveBeenCalledWith({ data: 'hello' });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should allow unsubscribing', () => {
|
|
55
|
-
const mockCb = vi.fn();
|
|
56
|
-
const unsub = emitter.on('test:event', mockCb);
|
|
57
|
-
unsub();
|
|
58
|
-
emitter.emit('test:event', { data: 'hello' });
|
|
59
|
-
expect(mockCb).not.toHaveBeenCalled();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should execute "once" subscriptions exactly once', () => {
|
|
63
|
-
const mockCb = vi.fn();
|
|
64
|
-
emitter.once('test:event', mockCb);
|
|
65
|
-
emitter.emit('test:event', { data: 'one' });
|
|
66
|
-
emitter.emit('test:event', { data: 'two' });
|
|
67
|
-
expect(mockCb).toHaveBeenCalledTimes(1);
|
|
68
|
-
expect(mockCb).toHaveBeenCalledWith({ data: 'one' });
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"declaration": true,
|
|
11
|
-
"baseUrl": ".",
|
|
12
|
-
"paths": {
|
|
13
|
-
"@/*": [
|
|
14
|
-
"src/*"
|
|
15
|
-
]
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"include": [
|
|
19
|
-
"src/**/*"
|
|
20
|
-
],
|
|
21
|
-
"exclude": [
|
|
22
|
-
"node_modules",
|
|
23
|
-
"dist",
|
|
24
|
-
"**/*.test.ts"
|
|
25
|
-
]
|
|
26
|
-
}
|
package/tsup.config.ts
DELETED