@nestia/e2e 7.4.0 → 8.0.0-dev.20250829
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/LICENSE +21 -21
- package/README.md +93 -93
- package/lib/ArrayUtil.d.ts +47 -43
- package/lib/ArrayUtil.js +122 -134
- package/lib/ArrayUtil.js.map +1 -1
- package/lib/GaffComparator.d.ts +19 -12
- package/lib/GaffComparator.js +19 -12
- package/lib/GaffComparator.js.map +1 -1
- package/lib/MapUtil.d.ts +79 -0
- package/lib/MapUtil.js +92 -0
- package/lib/MapUtil.js.map +1 -0
- package/lib/RandomGenerator.d.ts +107 -71
- package/lib/RandomGenerator.js +124 -109
- package/lib/RandomGenerator.js.map +1 -1
- package/lib/TestValidator.d.ts +107 -145
- package/lib/TestValidator.js +308 -351
- package/lib/TestValidator.js.map +1 -1
- package/lib/module.d.ts +2 -1
- package/lib/module.js +2 -1
- package/lib/module.js.map +1 -1
- package/package.json +1 -1
- package/src/ArrayUtil.ts +87 -88
- package/src/GaffComparator.ts +19 -12
- package/src/MapUtil.ts +86 -0
- package/src/RandomGenerator.ts +138 -101
- package/src/TestValidator.ts +251 -294
- package/src/module.ts +3 -1
package/lib/TestValidator.d.ts
CHANGED
|
@@ -7,21 +7,21 @@
|
|
|
7
7
|
* HTTP error validation, pagination testing, search functionality validation,
|
|
8
8
|
* and sorting validation.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* assertions.
|
|
10
|
+
* Most functions use direct parameter passing for simplicity, while some
|
|
11
|
+
* maintain currying patterns for advanced composition. All provide detailed
|
|
12
|
+
* error messages for debugging failed assertions.
|
|
13
13
|
*
|
|
14
14
|
* @author Jeongho Nam - https://github.com/samchon
|
|
15
15
|
* @example
|
|
16
16
|
* ```typescript
|
|
17
17
|
* // Basic condition testing
|
|
18
|
-
* TestValidator.predicate("user should be authenticated"
|
|
18
|
+
* TestValidator.predicate("user should be authenticated", user.isAuthenticated);
|
|
19
19
|
*
|
|
20
20
|
* // Equality validation
|
|
21
|
-
* TestValidator.equals("API response should match expected")
|
|
21
|
+
* TestValidator.equals("API response should match expected", x, y);
|
|
22
22
|
*
|
|
23
23
|
* // Error validation
|
|
24
|
-
* TestValidator.error("should throw on invalid input"
|
|
24
|
+
* TestValidator.error("should throw on invalid input", () => assertInput(""));
|
|
25
25
|
* ```;
|
|
26
26
|
*/
|
|
27
27
|
export declare namespace TestValidator {
|
|
@@ -35,23 +35,25 @@ export declare namespace TestValidator {
|
|
|
35
35
|
* @example
|
|
36
36
|
* ```typescript
|
|
37
37
|
* // Synchronous boolean
|
|
38
|
-
* TestValidator.predicate("user should exist"
|
|
38
|
+
* TestValidator.predicate("user should exist", user !== null);
|
|
39
39
|
*
|
|
40
40
|
* // Synchronous function
|
|
41
|
-
* TestValidator.predicate("array should be empty"
|
|
41
|
+
* TestValidator.predicate("array should be empty", () => arr.length === 0);
|
|
42
42
|
*
|
|
43
43
|
* // Asynchronous function
|
|
44
|
-
* await TestValidator.predicate("database should be connected"
|
|
44
|
+
* await TestValidator.predicate("database should be connected",
|
|
45
45
|
* async () => await db.ping()
|
|
46
46
|
* );
|
|
47
47
|
* ```;
|
|
48
48
|
*
|
|
49
49
|
* @param title - Descriptive title used in error messages when validation
|
|
50
50
|
* fails
|
|
51
|
-
* @
|
|
51
|
+
* @param condition - The condition to validate (boolean, function, or async
|
|
52
|
+
* function)
|
|
53
|
+
* @returns Void or Promise<void> based on the input type
|
|
52
54
|
* @throws Error with descriptive message when condition is not satisfied
|
|
53
55
|
*/
|
|
54
|
-
|
|
56
|
+
function predicate<T extends boolean | (() => boolean) | (() => Promise<boolean>)>(title: string, condition: T): T extends () => Promise<boolean> ? Promise<void> : void;
|
|
55
57
|
/**
|
|
56
58
|
* Validates deep equality between two values using JSON comparison.
|
|
57
59
|
*
|
|
@@ -59,48 +61,35 @@ export declare namespace TestValidator {
|
|
|
59
61
|
* exception filter to ignore specific keys during comparison. Useful for
|
|
60
62
|
* validating API responses, data transformations, and object state changes.
|
|
61
63
|
*
|
|
62
|
-
* **Type Safety Notes:**
|
|
63
|
-
*
|
|
64
|
-
* - The generic type T is inferred from the `actual` parameter (first in the
|
|
65
|
-
* currying chain)
|
|
66
|
-
* - The `expected` parameter must be assignable to `T | null | undefined`
|
|
67
|
-
* - For objects, `expected` must have the same or subset of properties as
|
|
68
|
-
* `actual`
|
|
69
|
-
* - For union types like `string | null`, ensure proper type compatibility:
|
|
70
|
-
*
|
|
71
|
-
* ```typescript
|
|
72
|
-
* const x: string | null;
|
|
73
|
-
* TestValidator.equals("works")(x)(null); // ✅ Works: null is assignable to string | null
|
|
74
|
-
* TestValidator.equals("error")(null)(x); // ❌ Error: x might be string, but expected is null
|
|
75
|
-
* ```
|
|
76
|
-
*
|
|
77
64
|
* @example
|
|
78
65
|
* ```typescript
|
|
79
66
|
* // Basic equality
|
|
80
|
-
* TestValidator.equals("response should match expected"
|
|
67
|
+
* TestValidator.equals("response should match expected", expectedUser, actualUser);
|
|
81
68
|
*
|
|
82
69
|
* // Ignore timestamps in comparison
|
|
83
|
-
* TestValidator.equals("user data should match",
|
|
84
|
-
*
|
|
85
|
-
* )
|
|
70
|
+
* TestValidator.equals("user data should match", expectedUser, actualUser,
|
|
71
|
+
* (key) => key === "updatedAt"
|
|
72
|
+
* );
|
|
86
73
|
*
|
|
87
74
|
* // Validate API response structure
|
|
88
|
-
*
|
|
89
|
-
*
|
|
75
|
+
* TestValidator.equals("API response structure",
|
|
76
|
+
* { id: 1, name: "John" },
|
|
77
|
+
* { id: 1, name: "John" }
|
|
78
|
+
* );
|
|
90
79
|
*
|
|
91
80
|
* // Type-safe nullable comparisons
|
|
92
81
|
* const nullableData: { name: string } | null = getData();
|
|
93
|
-
* TestValidator.equals("nullable check"
|
|
82
|
+
* TestValidator.equals("nullable check", nullableData, null);
|
|
94
83
|
* ```;
|
|
95
84
|
*
|
|
96
85
|
* @param title - Descriptive title used in error messages when values differ
|
|
86
|
+
* @param x - The first value to compare
|
|
87
|
+
* @param y - The second value to compare (can be null or undefined)
|
|
97
88
|
* @param exception - Optional filter function to exclude specific keys from
|
|
98
89
|
* comparison
|
|
99
|
-
* @returns A currying function chain: first accepts expected value, then
|
|
100
|
-
* actual value
|
|
101
90
|
* @throws Error with detailed diff information when values are not equal
|
|
102
91
|
*/
|
|
103
|
-
|
|
92
|
+
function equals<T>(title: string, x: T, y: T | null | undefined, exception?: (key: string) => boolean): void;
|
|
104
93
|
/**
|
|
105
94
|
* Validates deep inequality between two values using JSON comparison.
|
|
106
95
|
*
|
|
@@ -109,49 +98,33 @@ export declare namespace TestValidator {
|
|
|
109
98
|
* comparison. Useful for validating that data has changed, objects are
|
|
110
99
|
* different, or mutations have occurred.
|
|
111
100
|
*
|
|
112
|
-
* **Type Safety Notes:**
|
|
113
|
-
*
|
|
114
|
-
* - The generic type T is inferred from the `actual` parameter (first in the
|
|
115
|
-
* currying chain)
|
|
116
|
-
* - The `expected` parameter must be assignable to `T | null | undefined`
|
|
117
|
-
* - For objects, `expected` must have the same or subset of properties as
|
|
118
|
-
* `actual`
|
|
119
|
-
* - For union types like `string | null`, ensure proper type compatibility:
|
|
120
|
-
*
|
|
121
|
-
* ```typescript
|
|
122
|
-
* const x: string | null;
|
|
123
|
-
* TestValidator.notEquals("works")(x)(null); // ✅ Works: null is assignable to string | null
|
|
124
|
-
* TestValidator.notEquals("error")(null)(x); // ❌ Error: x might be string, but expected is null
|
|
125
|
-
* ```
|
|
126
|
-
*
|
|
127
101
|
* @example
|
|
128
102
|
* ```typescript
|
|
129
103
|
* // Basic inequality
|
|
130
|
-
* TestValidator.notEquals("user should be different after update"
|
|
104
|
+
* TestValidator.notEquals("user should be different after update", originalUser, updatedUser);
|
|
131
105
|
*
|
|
132
106
|
* // Ignore timestamps in comparison
|
|
133
|
-
* TestValidator.notEquals("user data should differ",
|
|
134
|
-
*
|
|
135
|
-
* )
|
|
107
|
+
* TestValidator.notEquals("user data should differ", originalUser, modifiedUser,
|
|
108
|
+
* (key) => key === "updatedAt"
|
|
109
|
+
* );
|
|
136
110
|
*
|
|
137
111
|
* // Validate state changes
|
|
138
|
-
*
|
|
139
|
-
* validateStateChange(initialState)(currentState);
|
|
112
|
+
* TestValidator.notEquals("state should have changed", initialState, currentState);
|
|
140
113
|
*
|
|
141
114
|
* // Type-safe nullable comparisons
|
|
142
115
|
* const mutableData: { count: number } | null = getMutableData();
|
|
143
|
-
* TestValidator.notEquals("should have changed"
|
|
116
|
+
* TestValidator.notEquals("should have changed", mutableData, null);
|
|
144
117
|
* ```;
|
|
145
118
|
*
|
|
146
119
|
* @param title - Descriptive title used in error messages when values are
|
|
147
120
|
* equal
|
|
121
|
+
* @param x - The first value to compare
|
|
122
|
+
* @param y - The second value to compare (can be null or undefined)
|
|
148
123
|
* @param exception - Optional filter function to exclude specific keys from
|
|
149
124
|
* comparison
|
|
150
|
-
* @returns A currying function chain: first accepts expected value, then
|
|
151
|
-
* actual value
|
|
152
125
|
* @throws Error when values are equal (indicating validation failure)
|
|
153
126
|
*/
|
|
154
|
-
|
|
127
|
+
function notEquals<T>(title: string, x: T, y: T | null | undefined, exception?: (key: string) => boolean): void;
|
|
155
128
|
/**
|
|
156
129
|
* Validates that a function throws an error or rejects when executed.
|
|
157
130
|
*
|
|
@@ -162,27 +135,28 @@ export declare namespace TestValidator {
|
|
|
162
135
|
* @example
|
|
163
136
|
* ```typescript
|
|
164
137
|
* // Synchronous error validation
|
|
165
|
-
* TestValidator.error("should reject invalid email"
|
|
138
|
+
* TestValidator.error("should reject invalid email",
|
|
166
139
|
* () => validateEmail("invalid-email")
|
|
167
140
|
* );
|
|
168
141
|
*
|
|
169
142
|
* // Asynchronous error validation
|
|
170
|
-
* await TestValidator.error("should reject unauthorized access"
|
|
143
|
+
* await TestValidator.error("should reject unauthorized access",
|
|
171
144
|
* async () => await api.functional.getSecretData()
|
|
172
145
|
* );
|
|
173
146
|
*
|
|
174
147
|
* // Validate input validation
|
|
175
|
-
* TestValidator.error("should throw on empty string"
|
|
148
|
+
* TestValidator.error("should throw on empty string",
|
|
176
149
|
* () => processRequiredInput("")
|
|
177
150
|
* );
|
|
178
151
|
* ```;
|
|
179
152
|
*
|
|
180
153
|
* @param title - Descriptive title used in error messages when no error
|
|
181
154
|
* occurs
|
|
182
|
-
* @
|
|
155
|
+
* @param task - The function that should throw an error or reject
|
|
156
|
+
* @returns Void or Promise<void> based on the input type
|
|
183
157
|
* @throws Error when the task function does not throw an error or reject
|
|
184
158
|
*/
|
|
185
|
-
|
|
159
|
+
function error<T>(title: string, task: () => T): T extends Promise<any> ? Promise<void> : void;
|
|
186
160
|
/**
|
|
187
161
|
* Validates that a function throws an HTTP error with specific status codes.
|
|
188
162
|
*
|
|
@@ -193,61 +167,29 @@ export declare namespace TestValidator {
|
|
|
193
167
|
* @example
|
|
194
168
|
* ```typescript
|
|
195
169
|
* // Validate 401 Unauthorized
|
|
196
|
-
* await TestValidator.httpError("should return 401 for invalid token"
|
|
170
|
+
* await TestValidator.httpError("should return 401 for invalid token", 401,
|
|
197
171
|
* async () => await api.functional.getProtectedResource("invalid-token")
|
|
198
172
|
* );
|
|
199
173
|
*
|
|
200
174
|
* // Validate multiple possible error codes
|
|
201
|
-
* await TestValidator.httpError("should return client error"
|
|
175
|
+
* await TestValidator.httpError("should return client error", [400, 404, 422],
|
|
202
176
|
* async () => await api.functional.updateNonexistentResource(data)
|
|
203
177
|
* );
|
|
204
178
|
*
|
|
205
179
|
* // Validate server errors
|
|
206
|
-
* TestValidator.httpError("should handle server errors"
|
|
180
|
+
* TestValidator.httpError("should handle server errors", [500, 502, 503],
|
|
207
181
|
* () => callFaultyEndpoint()
|
|
208
182
|
* );
|
|
209
183
|
* ```;
|
|
210
184
|
*
|
|
211
185
|
* @param title - Descriptive title used in error messages
|
|
212
|
-
* @
|
|
213
|
-
*
|
|
186
|
+
* @param status - Expected status code(s), can be a single number or array
|
|
187
|
+
* @param task - The function that should throw an HttpError
|
|
188
|
+
* @returns Void or Promise<void> based on the input type
|
|
214
189
|
* @throws Error when function doesn't throw HttpError or status code doesn't
|
|
215
190
|
* match
|
|
216
191
|
*/
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Safely executes a function and captures any errors without throwing.
|
|
220
|
-
*
|
|
221
|
-
* Utility function for error handling in tests. Executes the provided
|
|
222
|
-
* function and returns any error that occurs, or null if successful. Supports
|
|
223
|
-
* both synchronous and asynchronous functions. Useful for testing error
|
|
224
|
-
* conditions without stopping test execution.
|
|
225
|
-
*
|
|
226
|
-
* @example
|
|
227
|
-
* ```typescript
|
|
228
|
-
* // Synchronous error capture
|
|
229
|
-
* const error = TestValidator.proceed(() => {
|
|
230
|
-
* throw new Error("Something went wrong");
|
|
231
|
-
* });
|
|
232
|
-
* console.log(error?.message); // "Something went wrong"
|
|
233
|
-
*
|
|
234
|
-
* // Asynchronous error capture
|
|
235
|
-
* const asyncError = await TestValidator.proceed(async () => {
|
|
236
|
-
* await failingAsyncOperation();
|
|
237
|
-
* });
|
|
238
|
-
*
|
|
239
|
-
* // Success case
|
|
240
|
-
* const noError = TestValidator.proceed(() => {
|
|
241
|
-
* return "success";
|
|
242
|
-
* });
|
|
243
|
-
* console.log(noError); // null
|
|
244
|
-
* ```;
|
|
245
|
-
*
|
|
246
|
-
* @param task - Function to execute safely
|
|
247
|
-
* @returns Error object if function throws/rejects, null if successful
|
|
248
|
-
*/
|
|
249
|
-
function proceed(task: () => Promise<any>): Promise<Error | null>;
|
|
250
|
-
function proceed(task: () => any): Error | null;
|
|
192
|
+
function httpError<T>(title: string, status: number | number[], task: () => T): T extends Promise<any> ? Promise<void> : void;
|
|
251
193
|
/**
|
|
252
194
|
* Validates pagination index API results against expected entity order.
|
|
253
195
|
*
|
|
@@ -262,8 +204,7 @@ export declare namespace TestValidator {
|
|
|
262
204
|
* const expectedArticles = await db.articles.findAll({ order: 'created_at DESC' });
|
|
263
205
|
* const actualArticles = await api.functional.getArticles({ page: 1, limit: 10 });
|
|
264
206
|
*
|
|
265
|
-
* TestValidator.index("article pagination order"
|
|
266
|
-
* actualArticles,
|
|
207
|
+
* TestValidator.index("article pagination order", expectedArticles, actualArticles,
|
|
267
208
|
* true // enable trace logging
|
|
268
209
|
* );
|
|
269
210
|
*
|
|
@@ -271,16 +212,16 @@ export declare namespace TestValidator {
|
|
|
271
212
|
* const manuallyFilteredUsers = allUsers.filter(u => u.name.includes("John"));
|
|
272
213
|
* const apiSearchResults = await api.functional.searchUsers({ query: "John" });
|
|
273
214
|
*
|
|
274
|
-
* TestValidator.index("user search results"
|
|
275
|
-
* apiSearchResults
|
|
276
|
-
* );
|
|
215
|
+
* TestValidator.index("user search results", manuallyFilteredUsers, apiSearchResults);
|
|
277
216
|
* ```;
|
|
278
217
|
*
|
|
279
218
|
* @param title - Descriptive title used in error messages when order differs
|
|
280
|
-
* @
|
|
219
|
+
* @param expected - The expected entities in correct order
|
|
220
|
+
* @param gotten - The actual entities returned by the API
|
|
221
|
+
* @param trace - Optional flag to enable debug logging (default: false)
|
|
281
222
|
* @throws Error when entity order differs between expected and actual results
|
|
282
223
|
*/
|
|
283
|
-
const index:
|
|
224
|
+
const index: <Summary extends IEntity<any>>(title: string, expected: Summary[], gotten: Summary[], trace?: boolean) => void;
|
|
284
225
|
/**
|
|
285
226
|
* Validates search functionality by testing API results against manual
|
|
286
227
|
* filtering.
|
|
@@ -292,38 +233,49 @@ export declare namespace TestValidator {
|
|
|
292
233
|
*
|
|
293
234
|
* @example
|
|
294
235
|
* ```typescript
|
|
295
|
-
* // Test article search functionality
|
|
236
|
+
* // Test article search functionality with exact matching
|
|
296
237
|
* const allArticles = await db.articles.findAll();
|
|
297
|
-
* const searchValidator = TestValidator.search(
|
|
298
|
-
*
|
|
299
|
-
*
|
|
238
|
+
* const searchValidator = TestValidator.search(
|
|
239
|
+
* "article search API",
|
|
240
|
+
* (req) => api.searchArticles(req),
|
|
241
|
+
* allArticles,
|
|
242
|
+
* 5 // test with 5 random samples
|
|
243
|
+
* );
|
|
300
244
|
*
|
|
245
|
+
* // Test exact match search
|
|
301
246
|
* await searchValidator({
|
|
302
|
-
* fields: ["title"
|
|
303
|
-
* values: (article) => [article.title
|
|
304
|
-
* filter: (article, [
|
|
305
|
-
*
|
|
247
|
+
* fields: ["title"],
|
|
248
|
+
* values: (article) => [article.title], // full title for exact match
|
|
249
|
+
* filter: (article, [title]) => article.title === title, // exact match
|
|
250
|
+
* request: ([title]) => ({ search: { title } })
|
|
251
|
+
* });
|
|
252
|
+
*
|
|
253
|
+
* // Test partial match search with includes
|
|
254
|
+
* await searchValidator({
|
|
255
|
+
* fields: ["content"],
|
|
256
|
+
* values: (article) => [article.content.substring(0, 20)], // partial content
|
|
257
|
+
* filter: (article, [keyword]) => article.content.includes(keyword),
|
|
306
258
|
* request: ([keyword]) => ({ q: keyword })
|
|
307
259
|
* });
|
|
308
260
|
*
|
|
309
|
-
* // Test
|
|
310
|
-
* await
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* user.status === status && user.role === role,
|
|
317
|
-
* request: ([status, role]) => ({ status, role })
|
|
261
|
+
* // Test multi-field search with exact matching
|
|
262
|
+
* await searchValidator({
|
|
263
|
+
* fields: ["writer", "title"],
|
|
264
|
+
* values: (article) => [article.writer, article.title],
|
|
265
|
+
* filter: (article, [writer, title]) =>
|
|
266
|
+
* article.writer === writer && article.title === title,
|
|
267
|
+
* request: ([writer, title]) => ({ search: { writer, title } })
|
|
318
268
|
* });
|
|
319
269
|
* ```;
|
|
320
270
|
*
|
|
321
271
|
* @param title - Descriptive title used in error messages when search fails
|
|
322
|
-
* @
|
|
323
|
-
*
|
|
272
|
+
* @param getter - API function that performs the search
|
|
273
|
+
* @param total - Complete dataset to sample from for testing
|
|
274
|
+
* @param sampleCount - Number of random samples to test (default: 1)
|
|
275
|
+
* @returns A function that accepts search configuration properties
|
|
324
276
|
* @throws Error when API search results don't match manual filtering results
|
|
325
277
|
*/
|
|
326
|
-
const search:
|
|
278
|
+
const search: <Entity extends IEntity<any>, Request>(title: string, getter: (input: Request) => Promise<Entity[]>, total: Entity[], sampleCount?: number) => <Values extends any[]>(props: ISearchProps<Entity, Values, Request>) => Promise<void>;
|
|
327
279
|
/**
|
|
328
280
|
* Configuration interface for search validation functionality.
|
|
329
281
|
*
|
|
@@ -372,37 +324,47 @@ export declare namespace TestValidator {
|
|
|
372
324
|
*
|
|
373
325
|
* @example
|
|
374
326
|
* ```typescript
|
|
375
|
-
* // Test single field sorting
|
|
376
|
-
* const sortValidator = TestValidator.sort(
|
|
327
|
+
* // Test single field sorting with GaffComparator
|
|
328
|
+
* const sortValidator = TestValidator.sort(
|
|
329
|
+
* "article sorting",
|
|
377
330
|
* (sortable) => api.getArticles({ sort: sortable })
|
|
378
331
|
* )("created_at")(
|
|
379
|
-
* (a
|
|
332
|
+
* GaffComparator.dates((a) => a.created_at)
|
|
380
333
|
* );
|
|
381
334
|
*
|
|
382
335
|
* await sortValidator("+"); // ascending
|
|
383
336
|
* await sortValidator("-"); // descending
|
|
384
337
|
*
|
|
385
|
-
* // Test multi-field sorting with
|
|
386
|
-
* const userSortValidator = TestValidator.sort(
|
|
338
|
+
* // Test multi-field sorting with GaffComparator
|
|
339
|
+
* const userSortValidator = TestValidator.sort(
|
|
340
|
+
* "user sorting",
|
|
387
341
|
* (sortable) => api.getUsers({ sort: sortable })
|
|
388
|
-
* )("
|
|
389
|
-
* (
|
|
390
|
-
* if (a.status !== b.status) return a.status.localeCompare(b.status);
|
|
391
|
-
* return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
|
392
|
-
* },
|
|
342
|
+
* )("lastName", "firstName")(
|
|
343
|
+
* GaffComparator.strings((user) => [user.lastName, user.firstName]),
|
|
393
344
|
* (user) => user.isActive // only test active users
|
|
394
345
|
* );
|
|
395
346
|
*
|
|
396
347
|
* await userSortValidator("+", true); // ascending with trace logging
|
|
348
|
+
*
|
|
349
|
+
* // Custom comparator for complex logic
|
|
350
|
+
* const customSortValidator = TestValidator.sort(
|
|
351
|
+
* "custom sorting",
|
|
352
|
+
* (sortable) => api.getProducts({ sort: sortable })
|
|
353
|
+
* )("price", "rating")(
|
|
354
|
+
* (a, b) => {
|
|
355
|
+
* const priceDiff = a.price - b.price;
|
|
356
|
+
* return priceDiff !== 0 ? priceDiff : b.rating - a.rating; // price asc, rating desc
|
|
357
|
+
* }
|
|
358
|
+
* );
|
|
397
359
|
* ```;
|
|
398
360
|
*
|
|
399
361
|
* @param title - Descriptive title used in error messages when sorting fails
|
|
400
|
-
* @
|
|
401
|
-
*
|
|
362
|
+
* @param getter - API function that fetches sorted data
|
|
363
|
+
* @returns A currying function chain: field names, comparator, then direction
|
|
402
364
|
* @throws Error when API results are not properly sorted according to
|
|
403
365
|
* specification
|
|
404
366
|
*/
|
|
405
|
-
const sort:
|
|
367
|
+
const sort: <T extends object, Fields extends string, Sortable extends Array<`-${Fields}` | `+${Fields}`> = Array<`-${Fields}` | `+${Fields}`>>(title: string, getter: (sortable: Sortable) => Promise<T[]>) => (...fields: Fields[]) => (comp: (x: T, y: T) => number, filter?: (elem: T) => boolean) => (direction: "+" | "-", trace?: boolean) => Promise<void>;
|
|
406
368
|
/**
|
|
407
369
|
* Type alias for sortable field specifications.
|
|
408
370
|
*
|