@htlkg/core 0.0.2 → 0.0.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.
- package/README.md +51 -0
- package/dist/index.d.ts +219 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -8
- package/src/amplify-astro-adapter/amplify-astro-adapter.md +167 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.test.ts +296 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.ts +97 -0
- package/src/amplify-astro-adapter/createRunWithAmplifyServerContext.ts +84 -0
- package/src/amplify-astro-adapter/errors.test.ts +115 -0
- package/src/amplify-astro-adapter/errors.ts +105 -0
- package/src/amplify-astro-adapter/globalSettings.test.ts +78 -0
- package/src/amplify-astro-adapter/globalSettings.ts +16 -0
- package/src/amplify-astro-adapter/index.ts +14 -0
- package/src/amplify-astro-adapter/types.ts +55 -0
- package/src/auth/auth.md +178 -0
- package/src/auth/index.test.ts +180 -0
- package/src/auth/index.ts +294 -0
- package/src/constants/constants.md +132 -0
- package/src/constants/index.test.ts +116 -0
- package/src/constants/index.ts +98 -0
- package/src/core-exports.property.test.ts +186 -0
- package/src/errors/errors.md +177 -0
- package/src/errors/index.test.ts +153 -0
- package/src/errors/index.ts +134 -0
- package/src/index.ts +65 -0
- package/src/routes/index.ts +225 -0
- package/src/routes/routes.md +189 -0
- package/src/types/index.ts +94 -0
- package/src/types/types.md +144 -0
- package/src/utils/index.test.ts +257 -0
- package/src/utils/index.ts +112 -0
- package/src/utils/logger.ts +88 -0
- package/src/utils/utils.md +199 -0
- package/src/workspace.property.test.ts +235 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Routes Module
|
|
2
|
+
|
|
3
|
+
Type-safe route definitions with parameter interpolation. Eliminates string literals scattered across the codebase.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
routes,
|
|
10
|
+
adminRoutes,
|
|
11
|
+
portalRoutes,
|
|
12
|
+
authRoutes,
|
|
13
|
+
apiRoutes,
|
|
14
|
+
createRoute,
|
|
15
|
+
matchesRoute,
|
|
16
|
+
extractRouteParams,
|
|
17
|
+
navigateTo,
|
|
18
|
+
buildUrl,
|
|
19
|
+
} from '@htlkg/core/routes';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Route Objects
|
|
23
|
+
|
|
24
|
+
### adminRoutes
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
adminRoutes.dashboard() // "/admin"
|
|
28
|
+
adminRoutes.accounts() // "/admin/accounts"
|
|
29
|
+
adminRoutes.account({ id: 123 }) // "/admin/accounts/123"
|
|
30
|
+
adminRoutes.accountBrands({ id: 123 }) // "/admin/accounts/123/brands"
|
|
31
|
+
adminRoutes.brands() // "/admin/brands"
|
|
32
|
+
adminRoutes.brand({ id: 456 }) // "/admin/brands/456"
|
|
33
|
+
adminRoutes.brandSettings({ id: 456 }) // "/admin/brands/456/settings"
|
|
34
|
+
adminRoutes.users() // "/admin/users"
|
|
35
|
+
adminRoutes.user({ id: 'abc' }) // "/admin/users/abc"
|
|
36
|
+
adminRoutes.profile() // "/admin/user"
|
|
37
|
+
adminRoutes.analytics() // "/admin/analytics"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### portalRoutes
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
portalRoutes.home() // "/"
|
|
44
|
+
portalRoutes.brand({ brandId: 123 }) // "/brands/123"
|
|
45
|
+
portalRoutes.brandSettings({ brandId: 123 }) // "/brands/123/settings"
|
|
46
|
+
portalRoutes.brandDashboard({ brandId: 123 }) // "/brands/123/dashboard"
|
|
47
|
+
portalRoutes.brandAnalytics({ brandId: 123 }) // "/brands/123/analytics"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### authRoutes
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
authRoutes.login() // "/login"
|
|
54
|
+
authRoutes.logout() // "/api/auth/logout"
|
|
55
|
+
authRoutes.confirmSignIn() // "/api/auth/confirm-signin"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### apiRoutes
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
apiRoutes.auth.logout() // "/api/auth/logout"
|
|
62
|
+
apiRoutes.auth.confirmSignIn() // "/api/auth/confirm-signin"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Creating Custom Routes
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { createRoute } from '@htlkg/core/routes';
|
|
69
|
+
|
|
70
|
+
// Simple route
|
|
71
|
+
const homeRoute = createRoute('/home');
|
|
72
|
+
homeRoute(); // "/home"
|
|
73
|
+
|
|
74
|
+
// Route with parameters
|
|
75
|
+
const userRoute = createRoute<{ id: string }>('/users/:id');
|
|
76
|
+
userRoute({ id: '123' }); // "/users/123"
|
|
77
|
+
userRoute.path; // "/users/:id"
|
|
78
|
+
|
|
79
|
+
// Multiple parameters
|
|
80
|
+
const postRoute = createRoute<{ userId: string; postId: string }>(
|
|
81
|
+
'/users/:userId/posts/:postId'
|
|
82
|
+
);
|
|
83
|
+
postRoute({ userId: '1', postId: '42' }); // "/users/1/posts/42"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Utility Functions
|
|
87
|
+
|
|
88
|
+
### matchesRoute
|
|
89
|
+
|
|
90
|
+
Check if a path matches a route pattern.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { matchesRoute, adminRoutes } from '@htlkg/core/routes';
|
|
94
|
+
|
|
95
|
+
matchesRoute('/admin/accounts/123', adminRoutes.account); // true
|
|
96
|
+
matchesRoute('/admin/users', adminRoutes.account); // false
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### extractRouteParams
|
|
100
|
+
|
|
101
|
+
Extract parameters from a path.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { extractRouteParams, adminRoutes } from '@htlkg/core/routes';
|
|
105
|
+
|
|
106
|
+
const params = extractRouteParams('/admin/accounts/123', adminRoutes.account);
|
|
107
|
+
// { id: '123' }
|
|
108
|
+
|
|
109
|
+
const noMatch = extractRouteParams('/other/path', adminRoutes.account);
|
|
110
|
+
// null
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### navigateTo
|
|
114
|
+
|
|
115
|
+
Client-side navigation helper.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { navigateTo, adminRoutes } from '@htlkg/core/routes';
|
|
119
|
+
|
|
120
|
+
// Navigate to account page
|
|
121
|
+
navigateTo(adminRoutes.account({ id: 123 }));
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### buildUrl
|
|
125
|
+
|
|
126
|
+
Build URL with query parameters.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { buildUrl, adminRoutes } from '@htlkg/core/routes';
|
|
130
|
+
|
|
131
|
+
buildUrl(adminRoutes.users(), { page: 2, status: 'active' });
|
|
132
|
+
// "/admin/users?page=2&status=active"
|
|
133
|
+
|
|
134
|
+
buildUrl(adminRoutes.brands(), { search: null, page: 1 });
|
|
135
|
+
// "/admin/brands?page=1" (null values excluded)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Combined Routes Object
|
|
139
|
+
|
|
140
|
+
Access all routes through a single object:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { routes } from '@htlkg/core/routes';
|
|
144
|
+
|
|
145
|
+
routes.admin.dashboard()
|
|
146
|
+
routes.portal.brand({ brandId: 123 })
|
|
147
|
+
routes.auth.login()
|
|
148
|
+
routes.api.auth.logout()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## TypeScript Support
|
|
152
|
+
|
|
153
|
+
Routes are fully typed with parameter inference:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { adminRoutes } from '@htlkg/core/routes';
|
|
157
|
+
|
|
158
|
+
// TypeScript enforces correct parameters
|
|
159
|
+
adminRoutes.account({ id: 123 }); // ✓
|
|
160
|
+
adminRoutes.account({ id: '123' }); // ✓ (string | number)
|
|
161
|
+
adminRoutes.account({}); // ✗ Error: missing 'id'
|
|
162
|
+
adminRoutes.account({ foo: 'bar' }); // ✗ Error: unknown property
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Usage in Components
|
|
166
|
+
|
|
167
|
+
```astro
|
|
168
|
+
---
|
|
169
|
+
import { adminRoutes } from '@htlkg/core/routes';
|
|
170
|
+
|
|
171
|
+
const accountId = 123;
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
<a href={adminRoutes.account({ id: accountId })}>
|
|
175
|
+
View Account
|
|
176
|
+
</a>
|
|
177
|
+
|
|
178
|
+
<a href={adminRoutes.brands()}>
|
|
179
|
+
All Brands
|
|
180
|
+
</a>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// In Vue/React components
|
|
185
|
+
import { adminRoutes, buildUrl } from '@htlkg/core/routes';
|
|
186
|
+
|
|
187
|
+
const accountUrl = adminRoutes.account({ id: props.accountId });
|
|
188
|
+
const filteredUrl = buildUrl(adminRoutes.users(), { role: 'admin' });
|
|
189
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @htlkg/core/types
|
|
3
|
+
* Core TypeScript types and interfaces for Hotelinking applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AuthUser interface representing an authenticated user
|
|
8
|
+
* Re-exported from auth module for convenience
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthUser {
|
|
11
|
+
username: string;
|
|
12
|
+
email: string;
|
|
13
|
+
brandIds: number[];
|
|
14
|
+
accountIds: number[];
|
|
15
|
+
isAdmin: boolean;
|
|
16
|
+
isSuperAdmin: boolean;
|
|
17
|
+
roles: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Brand interface representing a hotel brand
|
|
22
|
+
*/
|
|
23
|
+
export interface Brand {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
accountId: string;
|
|
27
|
+
logo?: string;
|
|
28
|
+
timezone: string;
|
|
29
|
+
status: "active" | "inactive" | "maintenance" | "suspended";
|
|
30
|
+
settings: Record<string, any>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Account interface representing a customer account
|
|
35
|
+
*/
|
|
36
|
+
export interface Account {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
logo?: string;
|
|
40
|
+
subscription: Record<string, any>;
|
|
41
|
+
settings: Record<string, any>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Product interface representing a product definition
|
|
46
|
+
*/
|
|
47
|
+
export interface Product {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
version: string;
|
|
51
|
+
schema: Record<string, any>;
|
|
52
|
+
uiSchema: Record<string, any>;
|
|
53
|
+
defaultConfig: Record<string, any>;
|
|
54
|
+
isActive: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* ProductInstance interface representing an enabled product for a brand
|
|
59
|
+
*/
|
|
60
|
+
export interface ProductInstance {
|
|
61
|
+
id: string;
|
|
62
|
+
productId: string;
|
|
63
|
+
productName: string;
|
|
64
|
+
brandId: string;
|
|
65
|
+
accountId: string;
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
config: Record<string, any>;
|
|
68
|
+
version: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* User interface representing a system user
|
|
73
|
+
*/
|
|
74
|
+
export interface User {
|
|
75
|
+
id: string;
|
|
76
|
+
cognitoId: string;
|
|
77
|
+
email: string;
|
|
78
|
+
accountId: string;
|
|
79
|
+
brandIds?: string[];
|
|
80
|
+
roles?: string[];
|
|
81
|
+
permissions?: Record<string, any>;
|
|
82
|
+
lastLogin?: string;
|
|
83
|
+
status: "active" | "inactive" | "pending" | "suspended";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* BrandUser interface representing a user associated with a brand
|
|
88
|
+
*/
|
|
89
|
+
export interface BrandUser {
|
|
90
|
+
userId: string;
|
|
91
|
+
brandId: string;
|
|
92
|
+
role: string;
|
|
93
|
+
permissions: string[];
|
|
94
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Types Module
|
|
2
|
+
|
|
3
|
+
Core TypeScript interfaces for Hotelinking applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import type {
|
|
9
|
+
AuthUser,
|
|
10
|
+
Brand,
|
|
11
|
+
Account,
|
|
12
|
+
Product,
|
|
13
|
+
ProductInstance,
|
|
14
|
+
User,
|
|
15
|
+
BrandUser,
|
|
16
|
+
} from '@htlkg/core/types';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Interfaces
|
|
20
|
+
|
|
21
|
+
### AuthUser
|
|
22
|
+
|
|
23
|
+
Authenticated user from Cognito.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
interface AuthUser {
|
|
27
|
+
username: string;
|
|
28
|
+
email: string;
|
|
29
|
+
brandIds: number[];
|
|
30
|
+
accountIds: number[];
|
|
31
|
+
isAdmin: boolean;
|
|
32
|
+
isSuperAdmin: boolean;
|
|
33
|
+
roles: string[];
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Brand
|
|
38
|
+
|
|
39
|
+
Hotel brand entity.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
interface Brand {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
accountId: string;
|
|
46
|
+
logo?: string;
|
|
47
|
+
timezone: string;
|
|
48
|
+
status: 'active' | 'inactive' | 'maintenance' | 'suspended';
|
|
49
|
+
settings: Record<string, any>;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Account
|
|
55
|
+
|
|
56
|
+
Customer account entity.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
interface Account {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
logo?: string;
|
|
63
|
+
subscription: Record<string, any>;
|
|
64
|
+
settings: Record<string, any>;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Product
|
|
69
|
+
|
|
70
|
+
Product definition.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface Product {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
version: string;
|
|
77
|
+
schema: Record<string, any>;
|
|
78
|
+
uiSchema: Record<string, any>;
|
|
79
|
+
defaultConfig: Record<string, any>;
|
|
80
|
+
isActive: boolean;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### ProductInstance
|
|
85
|
+
|
|
86
|
+
Enabled product for a brand.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface ProductInstance {
|
|
90
|
+
id: string;
|
|
91
|
+
productId: string;
|
|
92
|
+
productName: string;
|
|
93
|
+
brandId: string;
|
|
94
|
+
accountId: string;
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
config: Record<string, any>;
|
|
97
|
+
version: string;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### User
|
|
102
|
+
|
|
103
|
+
System user entity.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface User {
|
|
107
|
+
id: string;
|
|
108
|
+
cognitoId: string;
|
|
109
|
+
email: string;
|
|
110
|
+
accountId: string;
|
|
111
|
+
brandIds?: string[];
|
|
112
|
+
roles?: string[];
|
|
113
|
+
permissions?: Record<string, any>;
|
|
114
|
+
lastLogin?: string;
|
|
115
|
+
status: 'active' | 'inactive' | 'pending' | 'suspended';
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### BrandUser
|
|
120
|
+
|
|
121
|
+
User-brand association.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
interface BrandUser {
|
|
125
|
+
userId: string;
|
|
126
|
+
brandId: string;
|
|
127
|
+
role: string;
|
|
128
|
+
permissions: string[];
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Usage Examples
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import type { Brand, User, ProductInstance } from '@htlkg/core/types';
|
|
136
|
+
|
|
137
|
+
function getBrandProducts(brand: Brand): ProductInstance[] {
|
|
138
|
+
// ...
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isUserActive(user: User): boolean {
|
|
142
|
+
return user.status === 'active';
|
|
143
|
+
}
|
|
144
|
+
```
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
formatDate,
|
|
4
|
+
truncateText,
|
|
5
|
+
groupBy,
|
|
6
|
+
debounce,
|
|
7
|
+
throttle,
|
|
8
|
+
} from "./index";
|
|
9
|
+
|
|
10
|
+
describe("Utility Functions", () => {
|
|
11
|
+
describe("formatDate", () => {
|
|
12
|
+
it("should format Date object", () => {
|
|
13
|
+
const date = new Date("2024-01-15T10:30:00Z");
|
|
14
|
+
const result = formatDate(date);
|
|
15
|
+
expect(result).toContain("Jan");
|
|
16
|
+
expect(result).toContain("15");
|
|
17
|
+
expect(result).toContain("2024");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should format date string", () => {
|
|
21
|
+
const result = formatDate("2024-01-15");
|
|
22
|
+
expect(result).toContain("Jan");
|
|
23
|
+
expect(result).toContain("15");
|
|
24
|
+
expect(result).toContain("2024");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should format timestamp", () => {
|
|
28
|
+
const timestamp = new Date("2024-01-15").getTime();
|
|
29
|
+
const result = formatDate(timestamp);
|
|
30
|
+
expect(result).toContain("Jan");
|
|
31
|
+
expect(result).toContain("15");
|
|
32
|
+
expect(result).toContain("2024");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should use custom locale", () => {
|
|
36
|
+
const date = new Date("2024-01-15");
|
|
37
|
+
const result = formatDate(date, "es-ES");
|
|
38
|
+
// Spanish month abbreviation
|
|
39
|
+
expect(result).toContain("ene");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should use custom options", () => {
|
|
43
|
+
const date = new Date("2024-01-15T10:30:00Z");
|
|
44
|
+
const result = formatDate(date, "en-US", {
|
|
45
|
+
year: "numeric",
|
|
46
|
+
month: "long",
|
|
47
|
+
day: "numeric",
|
|
48
|
+
hour: "2-digit",
|
|
49
|
+
minute: "2-digit",
|
|
50
|
+
});
|
|
51
|
+
expect(result).toContain("January");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("truncateText", () => {
|
|
56
|
+
it("should not truncate short text", () => {
|
|
57
|
+
const result = truncateText("Hello", 10);
|
|
58
|
+
expect(result).toBe("Hello");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should truncate long text", () => {
|
|
62
|
+
const result = truncateText("Hello World", 8);
|
|
63
|
+
expect(result).toBe("Hello...");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should use custom suffix", () => {
|
|
67
|
+
const result = truncateText("Hello World", 8, "…");
|
|
68
|
+
expect(result).toBe("Hello W…");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should handle exact length", () => {
|
|
72
|
+
const result = truncateText("Hello", 5);
|
|
73
|
+
expect(result).toBe("Hello");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should handle empty string", () => {
|
|
77
|
+
const result = truncateText("", 10);
|
|
78
|
+
expect(result).toBe("");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should account for suffix length", () => {
|
|
82
|
+
const result = truncateText("Hello World", 10, "...");
|
|
83
|
+
expect(result).toBe("Hello W...");
|
|
84
|
+
expect(result.length).toBe(10);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("groupBy", () => {
|
|
89
|
+
it("should group items by key", () => {
|
|
90
|
+
const items = [
|
|
91
|
+
{ id: 1, category: "A" },
|
|
92
|
+
{ id: 2, category: "B" },
|
|
93
|
+
{ id: 3, category: "A" },
|
|
94
|
+
];
|
|
95
|
+
const result = groupBy(items, (item) => item.category);
|
|
96
|
+
|
|
97
|
+
expect(result).toEqual({
|
|
98
|
+
A: [
|
|
99
|
+
{ id: 1, category: "A" },
|
|
100
|
+
{ id: 3, category: "A" },
|
|
101
|
+
],
|
|
102
|
+
B: [{ id: 2, category: "B" }],
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should handle numeric keys", () => {
|
|
107
|
+
const items = [
|
|
108
|
+
{ id: 1, value: 10 },
|
|
109
|
+
{ id: 2, value: 20 },
|
|
110
|
+
{ id: 3, value: 10 },
|
|
111
|
+
];
|
|
112
|
+
const result = groupBy(items, (item) => item.value);
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual({
|
|
115
|
+
10: [
|
|
116
|
+
{ id: 1, value: 10 },
|
|
117
|
+
{ id: 3, value: 10 },
|
|
118
|
+
],
|
|
119
|
+
20: [{ id: 2, value: 20 }],
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should handle empty array", () => {
|
|
124
|
+
const result = groupBy([], (item: any) => item.key);
|
|
125
|
+
expect(result).toEqual({});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should handle single item", () => {
|
|
129
|
+
const items = [{ id: 1, type: "test" }];
|
|
130
|
+
const result = groupBy(items, (item) => item.type);
|
|
131
|
+
|
|
132
|
+
expect(result).toEqual({
|
|
133
|
+
test: [{ id: 1, type: "test" }],
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("debounce", () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
vi.useFakeTimers();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterEach(() => {
|
|
144
|
+
vi.restoreAllMocks();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should delay function execution", () => {
|
|
148
|
+
const fn = vi.fn();
|
|
149
|
+
const debounced = debounce(fn, 100);
|
|
150
|
+
|
|
151
|
+
debounced();
|
|
152
|
+
expect(fn).not.toHaveBeenCalled();
|
|
153
|
+
|
|
154
|
+
vi.advanceTimersByTime(100);
|
|
155
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should cancel previous calls", () => {
|
|
159
|
+
const fn = vi.fn();
|
|
160
|
+
const debounced = debounce(fn, 100);
|
|
161
|
+
|
|
162
|
+
debounced();
|
|
163
|
+
debounced();
|
|
164
|
+
debounced();
|
|
165
|
+
|
|
166
|
+
vi.advanceTimersByTime(100);
|
|
167
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should pass arguments to function", () => {
|
|
171
|
+
const fn = vi.fn();
|
|
172
|
+
const debounced = debounce(fn, 100);
|
|
173
|
+
|
|
174
|
+
debounced("arg1", "arg2");
|
|
175
|
+
vi.advanceTimersByTime(100);
|
|
176
|
+
|
|
177
|
+
expect(fn).toHaveBeenCalledWith("arg1", "arg2");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should reset timer on each call", () => {
|
|
181
|
+
const fn = vi.fn();
|
|
182
|
+
const debounced = debounce(fn, 100);
|
|
183
|
+
|
|
184
|
+
debounced();
|
|
185
|
+
vi.advanceTimersByTime(50);
|
|
186
|
+
debounced();
|
|
187
|
+
vi.advanceTimersByTime(50);
|
|
188
|
+
|
|
189
|
+
expect(fn).not.toHaveBeenCalled();
|
|
190
|
+
|
|
191
|
+
vi.advanceTimersByTime(50);
|
|
192
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("throttle", () => {
|
|
197
|
+
beforeEach(() => {
|
|
198
|
+
vi.useFakeTimers();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
afterEach(() => {
|
|
202
|
+
vi.restoreAllMocks();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should execute function immediately", () => {
|
|
206
|
+
const fn = vi.fn();
|
|
207
|
+
const throttled = throttle(fn, 100);
|
|
208
|
+
|
|
209
|
+
throttled();
|
|
210
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should ignore calls within limit", () => {
|
|
214
|
+
const fn = vi.fn();
|
|
215
|
+
const throttled = throttle(fn, 100);
|
|
216
|
+
|
|
217
|
+
throttled();
|
|
218
|
+
throttled();
|
|
219
|
+
throttled();
|
|
220
|
+
|
|
221
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should allow calls after limit", () => {
|
|
225
|
+
const fn = vi.fn();
|
|
226
|
+
const throttled = throttle(fn, 100);
|
|
227
|
+
|
|
228
|
+
throttled();
|
|
229
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
230
|
+
|
|
231
|
+
vi.advanceTimersByTime(100);
|
|
232
|
+
throttled();
|
|
233
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should pass arguments to function", () => {
|
|
237
|
+
const fn = vi.fn();
|
|
238
|
+
const throttled = throttle(fn, 100);
|
|
239
|
+
|
|
240
|
+
throttled("arg1", "arg2");
|
|
241
|
+
expect(fn).toHaveBeenCalledWith("arg1", "arg2");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should throttle multiple rapid calls", () => {
|
|
245
|
+
const fn = vi.fn();
|
|
246
|
+
const throttled = throttle(fn, 100);
|
|
247
|
+
|
|
248
|
+
throttled();
|
|
249
|
+
vi.advanceTimersByTime(50);
|
|
250
|
+
throttled();
|
|
251
|
+
vi.advanceTimersByTime(50);
|
|
252
|
+
throttled();
|
|
253
|
+
|
|
254
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|