@nordcraft/core 2.0.3 → 2.0.5
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/dist/api/ToddleApiV2.js +5 -0
- package/dist/api/ToddleApiV2.js.map +1 -1
- package/dist/component/ToddleComponent.d.ts +5 -5
- package/dist/component/ToddleComponent.js +17 -13
- package/dist/component/ToddleComponent.js.map +1 -1
- package/dist/component/component.types.d.ts +22 -15
- package/dist/component/component.types.js.map +1 -1
- package/dist/formula/formula.d.ts +1 -1
- package/dist/formula/formula.js +66 -25
- package/dist/formula/formula.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/utils/collections.d.ts +1 -1
- package/dist/utils/collections.js +1 -1
- package/dist/utils/collections.js.map +1 -1
- package/dist/utils/getNodeSelector.js +10 -1
- package/dist/utils/getNodeSelector.js.map +1 -1
- package/dist/utils/measure.d.ts +1 -1
- package/dist/utils/measure.js +18 -4
- package/dist/utils/measure.js.map +1 -1
- package/package.json +1 -1
- package/src/api/ToddleApiV2.ts +5 -0
- package/src/component/ToddleComponent.ts +17 -13
- package/src/component/component.types.ts +28 -14
- package/src/formula/formula.ts +67 -25
- package/src/types.ts +6 -0
- package/src/utils/collections.ts +5 -2
- package/src/utils/getNodeSelector.test.ts +9 -0
- package/src/utils/getNodeSelector.ts +10 -1
- package/src/utils/measure.test.ts +149 -0
- package/src/utils/measure.ts +23 -4
package/dist/utils/measure.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
const globalScope = typeof globalThis !== 'undefined' ? globalThis : window;
|
|
2
|
-
globalScope.__nc_measure_max_depth =
|
|
2
|
+
globalScope.__nc_measure_max_depth =
|
|
3
|
+
typeof sessionStorage !== 'undefined' &&
|
|
4
|
+
sessionStorage.getItem('__nc_measure_max_depth')
|
|
5
|
+
? parseInt(sessionStorage.getItem('__nc_measure_max_depth'))
|
|
6
|
+
: 10;
|
|
3
7
|
globalScope.__nc_measure_enabled =
|
|
4
8
|
typeof sessionStorage !== 'undefined' &&
|
|
5
9
|
sessionStorage.getItem('__nc_measure') === 'true';
|
|
6
10
|
globalScope.__nc_enableMeasure = (enabled = true, maxDepth = 10) => {
|
|
7
11
|
if (enabled) {
|
|
8
12
|
sessionStorage.setItem('__nc_measure', 'true');
|
|
13
|
+
sessionStorage.setItem('__nc_measure_max_depth', String(maxDepth));
|
|
9
14
|
}
|
|
10
15
|
else {
|
|
11
16
|
sessionStorage.removeItem('__nc_measure');
|
|
17
|
+
sessionStorage.removeItem('__nc_measure_max_depth');
|
|
12
18
|
}
|
|
13
19
|
globalScope.__nc_measure_enabled = enabled;
|
|
14
20
|
globalScope.__nc_measure_max_depth = maxDepth;
|
|
@@ -16,15 +22,19 @@ globalScope.__nc_enableMeasure = (enabled = true, maxDepth = 10) => {
|
|
|
16
22
|
let measureCount = 0;
|
|
17
23
|
const STACK = [];
|
|
18
24
|
const NOOP = () => { };
|
|
19
|
-
export const measure = (key, details) => {
|
|
25
|
+
export const measure = (key, details, type) => {
|
|
20
26
|
if (!globalScope.__nc_measure_enabled) {
|
|
21
27
|
return NOOP;
|
|
22
28
|
}
|
|
23
29
|
const selfIndex = measureCount++;
|
|
30
|
+
const selfStackSize = STACK.length;
|
|
31
|
+
if (selfStackSize >= globalScope.__nc_measure_max_depth) {
|
|
32
|
+
return NOOP;
|
|
33
|
+
}
|
|
24
34
|
if (STACK.length >= globalScope.__nc_measure_max_depth) {
|
|
25
35
|
return NOOP;
|
|
26
36
|
}
|
|
27
|
-
const start = performance.now();
|
|
37
|
+
const start = performance.now() + selfStackSize * 0.001;
|
|
28
38
|
STACK.push(key);
|
|
29
39
|
let _stopped = false;
|
|
30
40
|
return (extraDetails) => {
|
|
@@ -32,7 +42,7 @@ export const measure = (key, details) => {
|
|
|
32
42
|
return;
|
|
33
43
|
}
|
|
34
44
|
_stopped = true;
|
|
35
|
-
const end = performance.now();
|
|
45
|
+
const end = performance.now() + selfStackSize * 0.001;
|
|
36
46
|
const mergedDetails = extraDetails
|
|
37
47
|
? { ...details, ...extraDetails }
|
|
38
48
|
: details;
|
|
@@ -43,6 +53,7 @@ export const measure = (key, details) => {
|
|
|
43
53
|
devtools: {
|
|
44
54
|
dataType: 'track-entry',
|
|
45
55
|
track: 'Nordcraft devtools',
|
|
56
|
+
color: COLOR_MAP[type],
|
|
46
57
|
properties: [
|
|
47
58
|
...Object.entries(mergedDetails).map(([k, v]) => [k, String(v)]),
|
|
48
59
|
['Stack', STACK.join(' > ')],
|
|
@@ -56,4 +67,7 @@ export const measure = (key, details) => {
|
|
|
56
67
|
STACK.pop();
|
|
57
68
|
};
|
|
58
69
|
};
|
|
70
|
+
const COLOR_MAP = {
|
|
71
|
+
component: 'secondary',
|
|
72
|
+
};
|
|
59
73
|
//# sourceMappingURL=measure.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measure.js","sourceRoot":"","sources":["../../src/utils/measure.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAQ,OAAO,UAAU,KAAK,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAA;AAChF,WAAW,CAAC,sBAAsB,
|
|
1
|
+
{"version":3,"file":"measure.js","sourceRoot":"","sources":["../../src/utils/measure.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/api/ToddleApiV2.ts
CHANGED
|
@@ -279,6 +279,11 @@ export class ToddleApiV2<Handler> implements ApiRequest {
|
|
|
279
279
|
globalFormulas: this.globalFormulas,
|
|
280
280
|
path: ['apis', apiKey, 'redirectRules', rule, 'formula'],
|
|
281
281
|
})
|
|
282
|
+
yield* getFormulasInFormula({
|
|
283
|
+
formula: value.statusCode,
|
|
284
|
+
globalFormulas: this.globalFormulas,
|
|
285
|
+
path: ['apis', apiKey, 'redirectRules', rule, 'statusCode'],
|
|
286
|
+
})
|
|
282
287
|
}
|
|
283
288
|
yield* getFormulasInFormula({
|
|
284
289
|
formula: api.isError?.formula,
|
|
@@ -398,27 +398,31 @@ export class ToddleComponent<Handler> {
|
|
|
398
398
|
})
|
|
399
399
|
}
|
|
400
400
|
for (const [formulaKey, formula] of Object.entries(this.formulas ?? {})) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
401
|
+
if (isDefined(formula)) {
|
|
402
|
+
yield* getFormulasInFormula({
|
|
403
|
+
formula: formula.formula,
|
|
404
|
+
globalFormulas,
|
|
405
|
+
path: ['formulas', formulaKey, 'formula'],
|
|
406
|
+
packageName,
|
|
407
|
+
})
|
|
408
|
+
}
|
|
407
409
|
}
|
|
408
410
|
for (const [variableKey, variable] of Object.entries(
|
|
409
411
|
this.variables ?? {},
|
|
410
412
|
)) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
413
|
+
if (isDefined(variable)) {
|
|
414
|
+
yield* getFormulasInFormula({
|
|
415
|
+
formula: variable.initialValue,
|
|
416
|
+
globalFormulas,
|
|
417
|
+
path: ['variables', variableKey, 'initialValue'],
|
|
418
|
+
packageName,
|
|
419
|
+
})
|
|
420
|
+
}
|
|
417
421
|
}
|
|
418
422
|
for (const [workflowKey, workflow] of Object.entries(
|
|
419
423
|
this.workflows ?? {},
|
|
420
424
|
)) {
|
|
421
|
-
for (const [actionKey, action] of workflow
|
|
425
|
+
for (const [actionKey, action] of workflow?.actions.entries() ?? []) {
|
|
422
426
|
yield* getFormulasInAction({
|
|
423
427
|
action,
|
|
424
428
|
globalFormulas,
|
|
@@ -93,15 +93,17 @@ export interface ElementNodeModel {
|
|
|
93
93
|
repeat?: Nullable<Formula>
|
|
94
94
|
repeatKey?: Nullable<Formula>
|
|
95
95
|
tag: string
|
|
96
|
-
attrs
|
|
96
|
+
attrs?: Nullable<Partial<Record<string, Formula>>>
|
|
97
97
|
style?: Nullable<NodeStyleModel>
|
|
98
98
|
variants?: Nullable<StyleVariant[]>
|
|
99
99
|
animations?: Nullable<Record<string, Record<string, AnimationKeyframe>>>
|
|
100
|
-
children
|
|
101
|
-
events
|
|
100
|
+
children?: Nullable<string[]>
|
|
101
|
+
events?: Nullable<Partial<Record<string, Nullable<EventModel>>>>
|
|
102
102
|
classes?: Nullable<Record<string, { formula?: Nullable<Formula> }>>
|
|
103
103
|
'style-variables'?: Nullable<Array<StyleVariable>>
|
|
104
104
|
customProperties?: Nullable<Record<CustomPropertyName, CustomProperty>>
|
|
105
|
+
// Legacy implementations added an invalid property. We'll keep it here to know we can safely remove it
|
|
106
|
+
styleVariables?: never
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
export interface ComponentNodeModel {
|
|
@@ -117,9 +119,9 @@ export interface ComponentNodeModel {
|
|
|
117
119
|
style?: Nullable<NodeStyleModel>
|
|
118
120
|
variants?: Nullable<StyleVariant[]>
|
|
119
121
|
animations?: Nullable<Record<string, Record<string, AnimationKeyframe>>>
|
|
120
|
-
attrs
|
|
121
|
-
children
|
|
122
|
-
events
|
|
122
|
+
attrs?: Nullable<Record<string, Formula>>
|
|
123
|
+
children?: Nullable<string[]>
|
|
124
|
+
events?: Nullable<Partial<Record<string, Nullable<EventModel>>>>
|
|
123
125
|
customProperties?: Nullable<Record<CustomPropertyName, CustomProperty>>
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -130,7 +132,9 @@ export interface SlotNodeModel {
|
|
|
130
132
|
condition?: Nullable<Formula>
|
|
131
133
|
repeat?: Nullable<never>
|
|
132
134
|
repeatKey?: Nullable<never>
|
|
133
|
-
children
|
|
135
|
+
children?: Nullable<string[]>
|
|
136
|
+
// slots cannot have events, but an empty object might have been declared regardless
|
|
137
|
+
events?: never
|
|
134
138
|
}
|
|
135
139
|
export type NodeModel =
|
|
136
140
|
| TextNodeModel
|
|
@@ -180,9 +184,9 @@ export interface Component {
|
|
|
180
184
|
// @deprecated - use route->path instead
|
|
181
185
|
page?: Nullable<string> // page url /projects/:id - only for pages
|
|
182
186
|
route?: Nullable<PageRoute>
|
|
183
|
-
attributes?: Nullable<Record<string, ComponentAttribute
|
|
184
|
-
variables?: Nullable<Record<string, ComponentVariable
|
|
185
|
-
formulas?: Nullable<Record<string, ComponentFormula
|
|
187
|
+
attributes?: Nullable<Record<string, Nullable<ComponentAttribute>>>
|
|
188
|
+
variables?: Nullable<Record<string, Nullable<ComponentVariable>>>
|
|
189
|
+
formulas?: Nullable<Record<string, Nullable<ComponentFormula>>>
|
|
186
190
|
contexts?: Nullable<
|
|
187
191
|
Record<
|
|
188
192
|
// `componentName` or `packageName/componentName` if the context comes from a different package than the component itself
|
|
@@ -190,10 +194,10 @@ export interface Component {
|
|
|
190
194
|
ComponentContext
|
|
191
195
|
>
|
|
192
196
|
>
|
|
193
|
-
workflows?: Nullable<Record<string, ComponentWorkflow
|
|
197
|
+
workflows?: Nullable<Record<string, Nullable<ComponentWorkflow>>>
|
|
194
198
|
apis?: Nullable<Record<string, Nullable<ComponentAPI>>>
|
|
195
199
|
nodes?: Nullable<Record<string, NodeModel>>
|
|
196
|
-
events?: Nullable<ComponentEvent[]>
|
|
200
|
+
events?: Nullable<Nullable<ComponentEvent>[]>
|
|
197
201
|
onLoad?: Nullable<EventModel>
|
|
198
202
|
onAttributeChange?: Nullable<EventModel>
|
|
199
203
|
// exported indicates that a component is exported in a package
|
|
@@ -310,7 +314,7 @@ export interface CustomActionModel {
|
|
|
310
314
|
package?: Nullable<string>
|
|
311
315
|
name: string
|
|
312
316
|
description?: Nullable<string>
|
|
313
|
-
group?: Nullable<
|
|
317
|
+
group?: Nullable<unknown>
|
|
314
318
|
data?: Nullable<string | number | boolean | Formula>
|
|
315
319
|
arguments?: Nullable<Partial<CustomActionArgument[]>>
|
|
316
320
|
events?: Nullable<Record<string, ActionModelActions>>
|
|
@@ -328,12 +332,16 @@ export interface SwitchActionModel {
|
|
|
328
332
|
}>
|
|
329
333
|
>
|
|
330
334
|
default?: Nullable<ActionModelActions>
|
|
335
|
+
// Should never be set
|
|
336
|
+
arguments?: never
|
|
331
337
|
}
|
|
332
338
|
|
|
333
339
|
export interface VariableActionModel {
|
|
334
340
|
type: 'SetVariable'
|
|
335
341
|
variable: string
|
|
336
342
|
data: Nullable<Formula>
|
|
343
|
+
// Arguments for this action should never be provided, but have been set sometimes
|
|
344
|
+
arguments?: never
|
|
337
345
|
}
|
|
338
346
|
|
|
339
347
|
export interface FetchActionModel {
|
|
@@ -355,6 +363,8 @@ export interface SetURLParameterAction {
|
|
|
355
363
|
parameter: string
|
|
356
364
|
data?: Nullable<Formula>
|
|
357
365
|
historyMode?: Nullable<'replace' | 'push'>
|
|
366
|
+
// Should never be set
|
|
367
|
+
arguments?: never
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
export interface SetMultiUrlParameterAction {
|
|
@@ -367,12 +377,14 @@ export interface EventActionModel {
|
|
|
367
377
|
type: 'TriggerEvent'
|
|
368
378
|
event: string
|
|
369
379
|
data?: Nullable<Formula>
|
|
380
|
+
// Should never be set
|
|
381
|
+
arguments?: never
|
|
370
382
|
}
|
|
371
383
|
|
|
372
384
|
export interface WorkflowActionModel {
|
|
373
385
|
type: 'TriggerWorkflow'
|
|
374
386
|
workflow: string
|
|
375
|
-
parameters
|
|
387
|
+
parameters?: Nullable<Record<string, { formula?: Nullable<Formula> }>>
|
|
376
388
|
callbacks?: Nullable<
|
|
377
389
|
Partial<Record<string, { actions?: Nullable<Partial<ActionModel[]>> }>>
|
|
378
390
|
>
|
|
@@ -383,6 +395,8 @@ export interface WorkflowCallbackActionModel {
|
|
|
383
395
|
type: 'TriggerWorkflowCallback'
|
|
384
396
|
event: string
|
|
385
397
|
data?: Nullable<Formula>
|
|
398
|
+
// Should never be set
|
|
399
|
+
arguments?: never
|
|
386
400
|
}
|
|
387
401
|
|
|
388
402
|
export type ActionModel =
|
package/src/formula/formula.ts
CHANGED
|
@@ -209,13 +209,55 @@ export const isToddleFormula = <Handler>(
|
|
|
209
209
|
|
|
210
210
|
export function applyFormula(
|
|
211
211
|
formula: Formula | string | number | undefined | null | boolean,
|
|
212
|
-
|
|
212
|
+
ctx: FormulaContext,
|
|
213
213
|
extendedPath?: Array<string | number> | undefined,
|
|
214
214
|
): any {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
// Short-circuit when not reporting to avoid unnecessary overhead of creating new objects and function
|
|
216
|
+
if (!ctx.reportFormulaEvaluation) {
|
|
217
|
+
if (!isFormula(formula)) {
|
|
218
|
+
return formula
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
switch (formula.type) {
|
|
222
|
+
case 'value':
|
|
223
|
+
return formula.value
|
|
224
|
+
case 'path':
|
|
225
|
+
return applyPathFormula(formula, ctx.data)
|
|
226
|
+
case 'switch':
|
|
227
|
+
return applySwitchFormula(formula, ctx)
|
|
228
|
+
case 'or':
|
|
229
|
+
return applyOrFormula(formula, ctx)
|
|
230
|
+
case 'and':
|
|
231
|
+
return applyAndFormula(formula, ctx)
|
|
232
|
+
case 'object':
|
|
233
|
+
return applyObjectFormula(formula, ctx)
|
|
234
|
+
case 'record':
|
|
235
|
+
return applyRecordFormula(formula, ctx)
|
|
236
|
+
case 'array':
|
|
237
|
+
return applyArrayFormula(formula, ctx)
|
|
238
|
+
case 'function':
|
|
239
|
+
return applyFunctionFormula(formula, ctx)
|
|
240
|
+
case 'apply':
|
|
241
|
+
return applyApplyFormula(formula, ctx)
|
|
242
|
+
default:
|
|
243
|
+
if (ctx.env?.logErrors) {
|
|
244
|
+
console.error('Could not recognize formula', formula)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (e) {
|
|
248
|
+
if (ctx.env?.logErrors) {
|
|
249
|
+
console.error(e)
|
|
250
|
+
}
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return undefined
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const jsonPath = [...(ctx.jsonPath ?? []), ...(extendedPath ?? [])]
|
|
258
|
+
const _ctx = { ...ctx, jsonPath }
|
|
259
|
+
const report = (value: any, p: Array<string | number> = jsonPath) => {
|
|
260
|
+
ctx.reportFormulaEvaluation?.(p, value, _ctx)
|
|
219
261
|
return value
|
|
220
262
|
}
|
|
221
263
|
|
|
@@ -228,58 +270,58 @@ export function applyFormula(
|
|
|
228
270
|
return report(formula.value)
|
|
229
271
|
}
|
|
230
272
|
case 'path': {
|
|
231
|
-
return report(applyPathFormula(formula,
|
|
273
|
+
return report(applyPathFormula(formula, _ctx.data))
|
|
232
274
|
}
|
|
233
275
|
case 'switch': {
|
|
234
276
|
if (
|
|
235
|
-
|
|
236
|
-
|
|
277
|
+
_ctx.reportFormulaEvaluation &&
|
|
278
|
+
_ctx.jsonPath.length < MAX_REPORT_DEPTH
|
|
237
279
|
) {
|
|
238
|
-
return report(applyEvaluateAllSwitchFormula(formula,
|
|
280
|
+
return report(applyEvaluateAllSwitchFormula(formula, _ctx))
|
|
239
281
|
}
|
|
240
|
-
return applySwitchFormula(formula,
|
|
282
|
+
return applySwitchFormula(formula, _ctx)
|
|
241
283
|
}
|
|
242
284
|
case 'or': {
|
|
243
285
|
if (
|
|
244
|
-
|
|
245
|
-
|
|
286
|
+
_ctx.reportFormulaEvaluation &&
|
|
287
|
+
_ctx.jsonPath.length < MAX_REPORT_DEPTH
|
|
246
288
|
) {
|
|
247
|
-
return report(applyEvaluateAllOrFormula(formula,
|
|
289
|
+
return report(applyEvaluateAllOrFormula(formula, _ctx))
|
|
248
290
|
}
|
|
249
|
-
return applyOrFormula(formula,
|
|
291
|
+
return applyOrFormula(formula, _ctx)
|
|
250
292
|
}
|
|
251
293
|
case 'and': {
|
|
252
294
|
if (
|
|
253
|
-
|
|
254
|
-
|
|
295
|
+
_ctx.reportFormulaEvaluation &&
|
|
296
|
+
_ctx.jsonPath.length < MAX_REPORT_DEPTH
|
|
255
297
|
) {
|
|
256
|
-
return report(applyEvaluateAllAndFormula(formula,
|
|
298
|
+
return report(applyEvaluateAllAndFormula(formula, _ctx))
|
|
257
299
|
}
|
|
258
|
-
return applyAndFormula(formula,
|
|
300
|
+
return applyAndFormula(formula, _ctx)
|
|
259
301
|
}
|
|
260
302
|
case 'object': {
|
|
261
|
-
return report(applyObjectFormula(formula,
|
|
303
|
+
return report(applyObjectFormula(formula, _ctx))
|
|
262
304
|
}
|
|
263
305
|
case 'record': {
|
|
264
306
|
// object used to be called record, there are still examples in the wild.
|
|
265
|
-
return report(applyRecordFormula(formula,
|
|
307
|
+
return report(applyRecordFormula(formula, _ctx))
|
|
266
308
|
}
|
|
267
309
|
case 'array': {
|
|
268
|
-
return report(applyArrayFormula(formula,
|
|
310
|
+
return report(applyArrayFormula(formula, _ctx))
|
|
269
311
|
}
|
|
270
312
|
case 'function': {
|
|
271
|
-
return report(applyFunctionFormula(formula,
|
|
313
|
+
return report(applyFunctionFormula(formula, _ctx))
|
|
272
314
|
}
|
|
273
315
|
case 'apply': {
|
|
274
|
-
return report(applyApplyFormula(formula,
|
|
316
|
+
return report(applyApplyFormula(formula, _ctx))
|
|
275
317
|
}
|
|
276
318
|
default:
|
|
277
|
-
if (
|
|
319
|
+
if (_ctx.env?.logErrors) {
|
|
278
320
|
console.error('Could not recognize formula', formula)
|
|
279
321
|
}
|
|
280
322
|
}
|
|
281
323
|
} catch (e) {
|
|
282
|
-
if (
|
|
324
|
+
if (_ctx.env?.logErrors) {
|
|
283
325
|
console.error(e)
|
|
284
326
|
}
|
|
285
327
|
return report(null)
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,12 @@ export type ActionHandler<Args = unknown[]> = (
|
|
|
37
37
|
) => void
|
|
38
38
|
env: ToddleEnv
|
|
39
39
|
abortSignal: AbortSignal
|
|
40
|
+
stores?: {
|
|
41
|
+
theme: {
|
|
42
|
+
set: (value: string | null) => void
|
|
43
|
+
get: () => string | null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
40
46
|
},
|
|
41
47
|
event?: Nullable<Event>,
|
|
42
48
|
) => void
|
package/src/utils/collections.ts
CHANGED
|
@@ -75,10 +75,13 @@ export const groupBy = <T>(items: T[], f: (t: T) => string) =>
|
|
|
75
75
|
return acc
|
|
76
76
|
}, {})
|
|
77
77
|
|
|
78
|
-
export const filterObject = <T>(
|
|
78
|
+
export const filterObject = <T, T2 extends T = T>(
|
|
79
79
|
object: Record<string, T>,
|
|
80
80
|
f: (kv: [string, T]) => boolean,
|
|
81
|
-
): Record<string,
|
|
81
|
+
): Record<string, T2> =>
|
|
82
|
+
Object.fromEntries(
|
|
83
|
+
Object.entries(object).filter((o): o is [string, T2] => f(o)),
|
|
84
|
+
)
|
|
82
85
|
|
|
83
86
|
export function get<T = any>(
|
|
84
87
|
collection: T,
|
|
@@ -54,4 +54,13 @@ describe('getNodeSelector', () => {
|
|
|
54
54
|
const selector = getNodeSelector(path)
|
|
55
55
|
expect(selector).toBe('[data-id="0.1\\/2"]')
|
|
56
56
|
})
|
|
57
|
+
|
|
58
|
+
test('should prefix selector with an underscore if it starts with a number', () => {
|
|
59
|
+
const path = '0.1\\/2'
|
|
60
|
+
const selector = getNodeSelector(path, {
|
|
61
|
+
componentName: '404',
|
|
62
|
+
nodeId: 'test-node',
|
|
63
|
+
})
|
|
64
|
+
expect(selector).toBe('[data-id="0.1\\/2"]._404\\:test-node')
|
|
65
|
+
})
|
|
57
66
|
})
|
|
@@ -19,7 +19,10 @@ export function getNodeSelector(
|
|
|
19
19
|
): string {
|
|
20
20
|
let selector = `[data-id="${path}"]`
|
|
21
21
|
if (componentName) {
|
|
22
|
-
selector
|
|
22
|
+
// Do not allow classes to start with a number, for example a page named "404" would result in a selector starting with a number which is invalid in CSS.
|
|
23
|
+
selector += startsWithNumber(componentName)
|
|
24
|
+
? `._${componentName}`
|
|
25
|
+
: `.${componentName}`
|
|
23
26
|
}
|
|
24
27
|
if (nodeId) {
|
|
25
28
|
selector += `\\:${nodeId}`
|
|
@@ -32,3 +35,9 @@ export function getNodeSelector(
|
|
|
32
35
|
|
|
33
36
|
return selector
|
|
34
37
|
}
|
|
38
|
+
|
|
39
|
+
function startsWithNumber(str: string): boolean {
|
|
40
|
+
if (!str) return false
|
|
41
|
+
const code = str.charCodeAt(0)
|
|
42
|
+
return code >= 48 && code <= 57
|
|
43
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test'
|
|
2
|
+
import { measure } from './measure'
|
|
3
|
+
|
|
4
|
+
const globalScope = globalThis as any
|
|
5
|
+
|
|
6
|
+
describe('measure', () => {
|
|
7
|
+
let originalPerformanceMeasure: any
|
|
8
|
+
let originalPerformanceNow: any
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Reset global state
|
|
12
|
+
globalScope.__nc_measure_enabled = false
|
|
13
|
+
globalScope.__nc_measure_max_depth = 10
|
|
14
|
+
|
|
15
|
+
// Mock performance
|
|
16
|
+
originalPerformanceMeasure = performance.measure
|
|
17
|
+
originalPerformanceNow = performance.now
|
|
18
|
+
performance.measure = () => ({}) as any
|
|
19
|
+
performance.now = () => 0
|
|
20
|
+
|
|
21
|
+
// Mock sessionStorage
|
|
22
|
+
globalScope.sessionStorage = {
|
|
23
|
+
getItem: () => null,
|
|
24
|
+
setItem: () => {},
|
|
25
|
+
removeItem: () => {},
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
performance.measure = originalPerformanceMeasure
|
|
31
|
+
performance.now = originalPerformanceNow
|
|
32
|
+
delete globalScope.sessionStorage
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('should do nothing if NOT enabled', () => {
|
|
36
|
+
const measureSpy = spyOn(performance, 'measure')
|
|
37
|
+
const key = 'test-key'
|
|
38
|
+
const stop = measure(key, { arg: 1 })
|
|
39
|
+
|
|
40
|
+
expect(stop).toBeDefined()
|
|
41
|
+
stop()
|
|
42
|
+
|
|
43
|
+
expect(measureSpy).not.toHaveBeenCalled()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('should call performance.measure when enabled', () => {
|
|
47
|
+
globalScope.__nc_measure_enabled = true
|
|
48
|
+
const measureSpy = spyOn(performance, 'measure')
|
|
49
|
+
|
|
50
|
+
let now = 0
|
|
51
|
+
performance.now = () => now++
|
|
52
|
+
|
|
53
|
+
const key = 'test-performance'
|
|
54
|
+
const stop = measure(key, { arg: 1 })
|
|
55
|
+
|
|
56
|
+
stop({ extra: 2 })
|
|
57
|
+
|
|
58
|
+
expect(measureSpy).toHaveBeenCalledWith(
|
|
59
|
+
key,
|
|
60
|
+
expect.objectContaining({
|
|
61
|
+
start: 0,
|
|
62
|
+
end: 1,
|
|
63
|
+
detail: expect.objectContaining({
|
|
64
|
+
devtools: expect.objectContaining({
|
|
65
|
+
track: 'Nordcraft devtools',
|
|
66
|
+
tooltipText: expect.stringContaining(key),
|
|
67
|
+
}),
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('should merge details and extraDetails', () => {
|
|
74
|
+
globalScope.__nc_measure_enabled = true
|
|
75
|
+
const measureSpy = spyOn(performance, 'measure')
|
|
76
|
+
|
|
77
|
+
const stop = measure('key', { original: 'value' })
|
|
78
|
+
stop({ extra: 'value' })
|
|
79
|
+
|
|
80
|
+
const lastCall = measureSpy.mock.calls[0]
|
|
81
|
+
const properties = lastCall[1].detail.devtools.properties
|
|
82
|
+
|
|
83
|
+
const propsMap = new Map(properties)
|
|
84
|
+
expect(propsMap.get('original')).toBe('value')
|
|
85
|
+
expect(propsMap.get('extra')).toBe('value')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('should respect max depth', () => {
|
|
89
|
+
globalScope.__nc_measure_enabled = true
|
|
90
|
+
globalScope.__nc_measure_max_depth = 2
|
|
91
|
+
const measureSpy = spyOn(performance, 'measure')
|
|
92
|
+
|
|
93
|
+
const stop1 = measure('depth-1', {})
|
|
94
|
+
const stop2 = measure('depth-2', {})
|
|
95
|
+
const stop3 = measure('depth-3', {}) // Should be NOOP
|
|
96
|
+
|
|
97
|
+
stop3()
|
|
98
|
+
expect(measureSpy).not.toHaveBeenCalled()
|
|
99
|
+
|
|
100
|
+
stop2()
|
|
101
|
+
expect(measureSpy).toHaveBeenCalledTimes(1)
|
|
102
|
+
|
|
103
|
+
stop1()
|
|
104
|
+
expect(measureSpy).toHaveBeenCalledTimes(2)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('should handle nested measures and display stack', () => {
|
|
108
|
+
globalScope.__nc_measure_enabled = true
|
|
109
|
+
const measureSpy = spyOn(performance, 'measure')
|
|
110
|
+
|
|
111
|
+
const stop1 = measure('parent', {})
|
|
112
|
+
const stop2 = measure('child', {})
|
|
113
|
+
|
|
114
|
+
stop2()
|
|
115
|
+
const childCall = measureSpy.mock.calls[0]
|
|
116
|
+
const childStack = new Map(childCall[1].detail.devtools.properties).get(
|
|
117
|
+
'Stack',
|
|
118
|
+
)
|
|
119
|
+
expect(childStack).toBe('parent > child')
|
|
120
|
+
|
|
121
|
+
stop1()
|
|
122
|
+
const parentCall = measureSpy.mock.calls[1]
|
|
123
|
+
const parentStack = new Map(parentCall[1].detail.devtools.properties).get(
|
|
124
|
+
'Stack',
|
|
125
|
+
)
|
|
126
|
+
expect(parentStack).toBe('parent')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('should only call finish once', () => {
|
|
130
|
+
globalScope.__nc_measure_enabled = true
|
|
131
|
+
const measureSpy = spyOn(performance, 'measure')
|
|
132
|
+
|
|
133
|
+
const stop = measure('once', {})
|
|
134
|
+
stop()
|
|
135
|
+
stop()
|
|
136
|
+
|
|
137
|
+
expect(measureSpy).toHaveBeenCalledTimes(1)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('global __nc_enableMeasure should update state', () => {
|
|
141
|
+
// We need to trigger the globalScope mock logic if we want to test the helper function
|
|
142
|
+
// But since it's defined in the module scope of measure.ts, it might already be there
|
|
143
|
+
if (typeof globalScope.__nc_enableMeasure === 'function') {
|
|
144
|
+
globalScope.__nc_enableMeasure(true, 5)
|
|
145
|
+
expect(globalScope.__nc_measure_enabled).toBe(true)
|
|
146
|
+
expect(globalScope.__nc_measure_max_depth).toBe(5)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
})
|