@nestia/e2e 0.4.0 → 0.4.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/lib/ArrayUtil.js.map +1 -1
- package/lib/DynamicExecutor.d.ts +4 -3
- package/lib/DynamicExecutor.js +2 -3
- package/lib/DynamicExecutor.js.map +1 -1
- package/lib/GaffComparator.js.map +1 -1
- package/lib/RandomGenerator.js +3 -5
- package/lib/RandomGenerator.js.map +1 -1
- package/lib/StopWatch.js.map +1 -1
- package/lib/TestValidator.d.ts +2 -2
- package/lib/TestValidator.js +34 -8
- package/lib/TestValidator.js.map +1 -1
- package/lib/internal/json_equal_to.js +2 -6
- package/lib/internal/json_equal_to.js.map +1 -1
- package/package.json +5 -7
- package/src/ArrayUtil.ts +98 -99
- package/src/DynamicExecutor.ts +217 -223
- package/src/GaffComparator.ts +69 -69
- package/src/RandomGenerator.ts +161 -165
- package/src/StopWatch.ts +38 -38
- package/src/TestValidator.ts +339 -341
- package/src/index.ts +4 -4
- package/src/internal/json_equal_to.ts +34 -38
- package/src/module.ts +6 -6
package/src/DynamicExecutor.ts
CHANGED
|
@@ -18,266 +18,260 @@ import { StopWatch } from "./StopWatch";
|
|
|
18
18
|
* @author Jeongho Nam - https://github.com/samchon
|
|
19
19
|
*/
|
|
20
20
|
export namespace DynamicExecutor {
|
|
21
|
+
/**
|
|
22
|
+
* Function type of a prefixed.
|
|
23
|
+
*
|
|
24
|
+
* @template Arguments Type of parameters
|
|
25
|
+
* @template Ret Type of return value
|
|
26
|
+
*/
|
|
27
|
+
export interface Closure<Arguments extends any[], Ret = any> {
|
|
28
|
+
(...args: Arguments): Promise<Ret>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for dynamic executor.
|
|
33
|
+
*/
|
|
34
|
+
export interface IOptions<Parameters extends any[], Ret = any> {
|
|
21
35
|
/**
|
|
22
|
-
*
|
|
36
|
+
* Prefix of function name.
|
|
37
|
+
*
|
|
38
|
+
* Every prefixed function will be executed.
|
|
23
39
|
*
|
|
24
|
-
*
|
|
25
|
-
* @template Ret Type of return value
|
|
40
|
+
* In other words, if a function name doesn't start with the prefix, then it would never be executed.
|
|
26
41
|
*/
|
|
27
|
-
|
|
28
|
-
(...args: Arguments): Promise<Ret>;
|
|
29
|
-
}
|
|
42
|
+
prefix: string;
|
|
30
43
|
|
|
31
44
|
/**
|
|
32
|
-
*
|
|
45
|
+
* Get parameters of a function.
|
|
46
|
+
*
|
|
47
|
+
* @param name Function name
|
|
48
|
+
* @returns Parameters
|
|
33
49
|
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Prefix of function name.
|
|
37
|
-
*
|
|
38
|
-
* Every prefixed function will be executed.
|
|
39
|
-
*
|
|
40
|
-
* In other words, if a function name doesn't start with the prefix, then it would never be executed.
|
|
41
|
-
*/
|
|
42
|
-
prefix: string;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get parameters of a function.
|
|
46
|
-
*
|
|
47
|
-
* @param name Function name
|
|
48
|
-
* @returns Parameters
|
|
49
|
-
*/
|
|
50
|
-
parameters: (name: string) => Parameters;
|
|
50
|
+
parameters: (name: string) => Parameters;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Filter function whether to run or not.
|
|
54
|
+
*
|
|
55
|
+
* @param name Function name
|
|
56
|
+
* @returns Whether to run or not
|
|
57
|
+
*/
|
|
58
|
+
filter?: (name: string) => boolean;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Wrapper of test function.
|
|
62
|
+
*
|
|
63
|
+
* If you specify this `wrapper` property, every dynamic functions
|
|
64
|
+
* loaded and called by this `DynamicExecutor` would be wrapped by
|
|
65
|
+
* the `wrapper` function.
|
|
66
|
+
*
|
|
67
|
+
* @param name Function name
|
|
68
|
+
* @param closure Function to be executed
|
|
69
|
+
* @param parameters Parameters, result of options.parameters function.
|
|
70
|
+
* @returns Wrapper function
|
|
71
|
+
*/
|
|
72
|
+
wrapper?: (
|
|
73
|
+
name: string,
|
|
74
|
+
closure: Closure<Parameters, Ret>,
|
|
75
|
+
paramters: Parameters,
|
|
76
|
+
) => Promise<any>;
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Whether to show elapsed time on `console` or not.
|
|
80
|
+
*
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
showElapsedTime?: boolean;
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Extension of dynamic functions.
|
|
87
|
+
*
|
|
88
|
+
* @default js
|
|
89
|
+
*/
|
|
90
|
+
extension?: string;
|
|
91
|
+
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Report, result of dynamic execution.
|
|
95
|
+
*/
|
|
96
|
+
export interface IReport {
|
|
91
97
|
/**
|
|
92
|
-
*
|
|
98
|
+
* Location path of dynamic functions.
|
|
93
99
|
*/
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Location path of dynamic functions.
|
|
97
|
-
*/
|
|
98
|
-
location: string;
|
|
100
|
+
location: string;
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Execution results of dynamic functions.
|
|
104
|
+
*/
|
|
105
|
+
executions: IReport.IExecution[];
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Total elapsed time.
|
|
109
|
+
*/
|
|
110
|
+
time: number;
|
|
111
|
+
}
|
|
112
|
+
export namespace IReport {
|
|
113
|
+
/**
|
|
114
|
+
* Execution result of a dynamic function.
|
|
115
|
+
*/
|
|
116
|
+
export interface IExecution {
|
|
117
|
+
/**
|
|
118
|
+
* Name of function.
|
|
119
|
+
*/
|
|
120
|
+
name: string;
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Location path of the function.
|
|
124
|
+
*/
|
|
125
|
+
location: string;
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Error when occured.
|
|
129
|
+
*/
|
|
130
|
+
error: Error | null;
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
132
|
+
/**
|
|
133
|
+
* Elapsed time.
|
|
134
|
+
*/
|
|
135
|
+
time: number;
|
|
135
136
|
}
|
|
137
|
+
}
|
|
136
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Prepare dynamic executor in strict mode.
|
|
141
|
+
*
|
|
142
|
+
* In strict mode, if any error occurs, the program will be terminated directly.
|
|
143
|
+
* Otherwise, {@link validate} mode does not terminate when error occurs, but
|
|
144
|
+
* just archive the error log.
|
|
145
|
+
*
|
|
146
|
+
* @param options Options of dynamic executor
|
|
147
|
+
* @returns Runner of dynamic functions with specific location
|
|
148
|
+
*/
|
|
149
|
+
export const assert =
|
|
150
|
+
<Arguments extends any[]>(options: IOptions<Arguments>) =>
|
|
137
151
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* In strict mode, if any error occurs, the program will be terminated directly.
|
|
141
|
-
* Otherwise, {@link validate} mode does not terminate when error occurs, but
|
|
142
|
-
* just archive the error log.
|
|
152
|
+
* Run dynamic executor.
|
|
143
153
|
*
|
|
144
|
-
* @param
|
|
145
|
-
* @returns Runner of dynamic functions with specific location
|
|
154
|
+
* @param path Location of prefixed functions
|
|
146
155
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Run dynamic executor.
|
|
151
|
-
*
|
|
152
|
-
* @param path Location of prefixed functions
|
|
153
|
-
*/
|
|
154
|
-
(path: string): Promise<IReport> =>
|
|
155
|
-
main(options)(true)(path);
|
|
156
|
+
(path: string): Promise<IReport> =>
|
|
157
|
+
main(options)(true)(path);
|
|
156
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Prepare dynamic executor in loose mode.
|
|
161
|
+
*
|
|
162
|
+
* In loose mode, the program would not be terminated even when error occurs.
|
|
163
|
+
* Instead, the error would be archived and returns as a list. Otherwise,
|
|
164
|
+
* {@link assert} mode terminates the program directly when error occurs.
|
|
165
|
+
*
|
|
166
|
+
* @param options Options of dynamic executor
|
|
167
|
+
* @returns Runner of dynamic functions with specific location
|
|
168
|
+
*/
|
|
169
|
+
export const validate =
|
|
170
|
+
<Arguments extends any[]>(options: IOptions<Arguments>) =>
|
|
157
171
|
/**
|
|
158
|
-
*
|
|
172
|
+
* Run dynamic executor.
|
|
159
173
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* {@link assert} mode terminates the program directly when error occurs.
|
|
163
|
-
*
|
|
164
|
-
* @param options Options of dynamic executor
|
|
165
|
-
* @returns Runner of dynamic functions with specific location
|
|
174
|
+
* @param path Location of prefix functions
|
|
175
|
+
* @returns List of errors
|
|
166
176
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Run dynamic executor.
|
|
171
|
-
*
|
|
172
|
-
* @param path Location of prefix functions
|
|
173
|
-
* @returns List of errors
|
|
174
|
-
*/
|
|
175
|
-
(path: string): Promise<IReport> =>
|
|
176
|
-
main(options)(false)(path);
|
|
177
|
+
(path: string): Promise<IReport> =>
|
|
178
|
+
main(options)(false)(path);
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
180
|
+
const main =
|
|
181
|
+
<Arguments extends any[]>(options: IOptions<Arguments>) =>
|
|
182
|
+
(assert: boolean) =>
|
|
183
|
+
async (path: string) => {
|
|
184
|
+
const report: IReport = {
|
|
185
|
+
location: path,
|
|
186
|
+
time: Date.now(),
|
|
187
|
+
executions: [],
|
|
188
|
+
};
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
const executor = execute(options)(report)(assert);
|
|
191
|
+
const iterator = iterate(options.extension ?? "js")(executor);
|
|
192
|
+
await iterator(path);
|
|
191
193
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
report.time = Date.now() - report.time;
|
|
195
|
+
return report;
|
|
196
|
+
};
|
|
195
197
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for (const file of directory) {
|
|
207
|
-
const location: string = NodePath.resolve(
|
|
208
|
-
`${path}/${file}`,
|
|
209
|
-
);
|
|
210
|
-
const stats: fs.Stats = await fs.promises.lstat(location);
|
|
198
|
+
const iterate =
|
|
199
|
+
(extension: string) =>
|
|
200
|
+
<Arguments extends any[]>(
|
|
201
|
+
executor: (path: string, modulo: Module<Arguments>) => Promise<void>,
|
|
202
|
+
) => {
|
|
203
|
+
const visitor = async (path: string): Promise<void> => {
|
|
204
|
+
const directory: string[] = await fs.promises.readdir(path);
|
|
205
|
+
for (const file of directory) {
|
|
206
|
+
const location: string = NodePath.resolve(`${path}/${file}`);
|
|
207
|
+
const stats: fs.Stats = await fs.promises.lstat(location);
|
|
211
208
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
if (stats.isDirectory() === true) {
|
|
210
|
+
await visitor(location);
|
|
211
|
+
continue;
|
|
212
|
+
} else if (file.substr(-3) !== `.${extension}`) continue;
|
|
216
213
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
const modulo: Module<Arguments> = await import(location);
|
|
215
|
+
await executor(location, modulo);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
return visitor;
|
|
219
|
+
};
|
|
223
220
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
continue;
|
|
221
|
+
const execute =
|
|
222
|
+
<Arguments extends any[]>(options: IOptions<Arguments>) =>
|
|
223
|
+
(report: IReport) =>
|
|
224
|
+
(assert: boolean) =>
|
|
225
|
+
async (location: string, modulo: Module<Arguments>): Promise<void> => {
|
|
226
|
+
for (const [key, closure] of Object.entries(modulo)) {
|
|
227
|
+
if (
|
|
228
|
+
key.substring(0, options.prefix.length) !== options.prefix ||
|
|
229
|
+
typeof closure !== "function" ||
|
|
230
|
+
(options.filter && options.filter(key) === false)
|
|
231
|
+
)
|
|
232
|
+
continue;
|
|
237
233
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
234
|
+
const func = async () => {
|
|
235
|
+
if (options.wrapper !== undefined)
|
|
236
|
+
await options.wrapper(key, closure, options.parameters(key));
|
|
237
|
+
else await closure(...options.parameters(key));
|
|
238
|
+
};
|
|
239
|
+
const label: string = chalk.greenBright(key);
|
|
244
240
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
241
|
+
const result: IReport.IExecution = {
|
|
242
|
+
name: key,
|
|
243
|
+
location,
|
|
244
|
+
error: null,
|
|
245
|
+
time: Date.now(),
|
|
246
|
+
};
|
|
247
|
+
report.executions.push(result);
|
|
252
248
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
249
|
+
try {
|
|
250
|
+
if (options.showElapsedTime === false) {
|
|
251
|
+
await func();
|
|
252
|
+
result.time = Date.now() - result.time;
|
|
253
|
+
console.log(` - ${label}`);
|
|
254
|
+
} else {
|
|
255
|
+
result.time = (await StopWatch.measure(func))[1];
|
|
256
|
+
console.log(
|
|
257
|
+
` - ${label}: ${chalk.yellowBright(
|
|
258
|
+
result.time.toLocaleString(),
|
|
259
|
+
)} ms`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
} catch (exp) {
|
|
263
|
+
result.time = Date.now() - result.time;
|
|
264
|
+
result.error = exp as Error;
|
|
269
265
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
};
|
|
266
|
+
console.log(
|
|
267
|
+
` - ${label} -> ${chalk.redBright((exp as Error)?.name)}`,
|
|
268
|
+
);
|
|
269
|
+
if (assert === true) throw exp;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
279
273
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
274
|
+
interface Module<Arguments extends any[]> {
|
|
275
|
+
[key: string]: Closure<Arguments>;
|
|
276
|
+
}
|
|
283
277
|
}
|
package/src/GaffComparator.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gaff comparator.
|
|
3
|
-
*
|
|
4
|
-
* `GaffComparator` is a set of comparator functions for `Array.sort()` function,
|
|
5
|
-
* which can be used with {@link TestValidator.sort} function. If you want to see
|
|
6
|
-
* how to use them, see the below example link.
|
|
7
|
-
*
|
|
8
|
-
* @example https://github.com/samchon/nestia-template/blob/master/src/test/features/api/bbs/test_api_bbs_article_index_sort.ts
|
|
9
|
-
* @author Jeongho Nam - https://github.com/samchon
|
|
10
|
-
*/
|
|
11
|
-
export namespace GaffComparator {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Gaff comparator.
|
|
3
|
+
*
|
|
4
|
+
* `GaffComparator` is a set of comparator functions for `Array.sort()` function,
|
|
5
|
+
* which can be used with {@link TestValidator.sort} function. If you want to see
|
|
6
|
+
* how to use them, see the below example link.
|
|
7
|
+
*
|
|
8
|
+
* @example https://github.com/samchon/nestia-template/blob/master/src/test/features/api/bbs/test_api_bbs_article_index_sort.ts
|
|
9
|
+
* @author Jeongho Nam - https://github.com/samchon
|
|
10
|
+
*/
|
|
11
|
+
export namespace GaffComparator {
|
|
12
|
+
/**
|
|
13
|
+
* String(s) comparator.
|
|
14
|
+
*
|
|
15
|
+
* @param getter Getter of string(s) from input
|
|
16
|
+
* @returns Comparator function
|
|
17
|
+
*/
|
|
18
|
+
export const strings =
|
|
19
|
+
<T>(getter: (input: T) => string | string[]) =>
|
|
20
|
+
(x: T, y: T) => {
|
|
21
|
+
const a: string[] = wrap(getter(x));
|
|
22
|
+
const b: string[] = wrap(getter(y));
|
|
23
|
+
|
|
24
|
+
const idx: number = a.findIndex((v, i) => v !== b[i]);
|
|
25
|
+
return idx !== -1 ? compare(a[idx], b[idx]) : 0;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Date(s) comparator.
|
|
30
|
+
*
|
|
31
|
+
* @param getter Getter of date(s) from input
|
|
32
|
+
* @returns Comparator function
|
|
33
|
+
*/
|
|
34
|
+
export const dates =
|
|
35
|
+
<T>(getter: (input: T) => string | string[]) =>
|
|
36
|
+
(x: T, y: T) => {
|
|
37
|
+
const take = (v: T) =>
|
|
38
|
+
wrap(getter(v)).map((str) => new Date(str).getTime());
|
|
39
|
+
const a: number[] = take(x);
|
|
40
|
+
const b: number[] = take(y);
|
|
41
|
+
|
|
42
|
+
const idx: number = a.findIndex((v, i) => v !== b[i]);
|
|
43
|
+
return idx !== -1 ? a[idx] - b[idx] : 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Number(s) comparator.
|
|
48
|
+
*
|
|
49
|
+
* @param closure Getter of number(s) from input
|
|
50
|
+
* @returns Comparator function
|
|
51
|
+
*/
|
|
52
|
+
export const numbers =
|
|
53
|
+
<T>(closure: (input: T) => number | number[]) =>
|
|
54
|
+
(x: T, y: T) => {
|
|
55
|
+
const a: number[] = wrap(closure(x));
|
|
56
|
+
const b: number[] = wrap(closure(y));
|
|
57
|
+
|
|
58
|
+
const idx: number = a.findIndex((v, i) => v !== b[i]);
|
|
59
|
+
return idx !== -1 ? a[idx] - b[idx] : 0;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function compare(x: string, y: string) {
|
|
63
|
+
return x.localeCompare(y);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function wrap<T>(elem: T | T[]): T[] {
|
|
67
|
+
return Array.isArray(elem) ? elem : [elem];
|
|
68
|
+
}
|
|
69
|
+
}
|