• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-webcomponents / 15061703873

16 May 2025 05:57AM UTC coverage: 98.285%. Remained the same
15061703873

Pull #1689

github

web-flow
Merge 024952cf2 into 38f25e4f6
Pull Request #1689: refactor: Simplify iterNodes implementation

4587 of 4817 branches covered (95.23%)

Branch coverage included in aggregate %.

38 of 40 new or added lines in 3 files covered. (95.0%)

3 existing lines in 1 file now uncovered.

29395 of 29758 relevant lines covered (98.78%)

438.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.96
/src/components/common/util.ts
1
export interface PartNameInfo {
67✔
2
  readonly [name: string]: string | boolean | number;
67✔
3
}
67✔
4

67✔
5
export const partNameMap = (partNameInfo: PartNameInfo) => {
67✔
6
  return Object.keys(partNameInfo)
50,791✔
7
    .filter((key) => partNameInfo[key])
50,791✔
8
    .join(' ');
50,791✔
9
};
50,791✔
10

67✔
11
export function noop() {}
67✔
12

67✔
13
export const asPercent = (part: number, whole: number) => (part / whole) * 100;
67✔
14

67✔
15
export const clamp = (number: number, min: number, max: number) =>
67✔
16
  Math.max(min, Math.min(number, max));
2,824✔
17

67✔
18
export function numberOfDecimals(number: number): number {
67✔
19
  const [_, decimals] = number.toString().split('.');
122✔
20
  return decimals ? decimals.length : 0;
122✔
21
}
122✔
22

67✔
23
export function roundPrecise(number: number, magnitude = 1): number {
67✔
24
  const factor = 10 ** magnitude;
122✔
25
  return Math.round(number * factor) / factor;
122✔
26
}
122✔
27

67✔
28
export function numberInRangeInclusive(
67✔
29
  value: number,
4✔
30
  min: number,
4✔
31
  max: number
4✔
32
) {
4✔
33
  return value >= min && value <= max;
4!
34
}
4✔
35

67✔
36
export function createCounter() {
67✔
37
  let i = 0;
147✔
38
  return () => {
147✔
39
    i++;
2,256✔
40
    return i;
2,256✔
41
  };
2,256✔
42
}
147✔
43

67✔
44
/**
67✔
45
 * Returns whether an element has a Left-to-Right directionality.
67✔
46
 */
67✔
47
export function isLTR(element: HTMLElement) {
67✔
48
  return element.matches(':dir(ltr)');
2,964✔
49
}
2,964✔
50

67✔
51
/**
67✔
52
 * Builds a string from format specifiers and replacement parameters.
67✔
53
 * Will coerce non-string parameters to their string representations.
67✔
54
 *
67✔
55
 * @example
67✔
56
 * ```typescript
67✔
57
 * formatString('{0} says "{1}".', 'John', 'Hello'); // 'John says "Hello".'
67✔
58
 * formatString('{1} is greater than {0}', 0, 1); // '1 is greater than 0'
67✔
59
 * ```
67✔
60
 */
67✔
61
export function formatString(template: string, ...params: unknown[]): string {
67✔
62
  const length = params.length;
616✔
63

616✔
64
  return template.replace(/{(\d+)}/g, (match: string, index: number) =>
616✔
65
    index >= length ? match : `${params[index]}`
878✔
66
  );
616✔
67
}
616✔
68

67✔
69
/**
67✔
70
 * Parse the passed `value` as a number or return the `fallback` if it can't be done.
67✔
71
 *
67✔
72
 * @example
67✔
73
 * ```typescript
67✔
74
 * asNumber('5'); // 5
67✔
75
 * asNumber('3.14'); // 3.14
67✔
76
 * asNumber('five'); // 0
67✔
77
 * asNUmber('five', 5); // 5
67✔
78
 * ```
67✔
79
 */
67✔
80
export function asNumber(value: unknown, fallback = 0) {
67✔
81
  const parsed = Number.parseFloat(value as string);
1,586✔
82
  return Number.isNaN(parsed) ? fallback : parsed;
1,586✔
83
}
1,586✔
84

67✔
85
/**
67✔
86
 * Returns the value wrapped between the min and max bounds.
67✔
87
 *
67✔
88
 * If the value is greater than max, returns the min and vice-versa.
67✔
89
 * If the value is between the bounds, it is returned unchanged.
67✔
90
 *
67✔
91
 * @example
67✔
92
 * ```typescript
67✔
93
 * wrap(1, 4, 2); // 2
67✔
94
 * wrap(1, 4, 5); // 1
67✔
95
 * wrap(1, 4, -1); // 4
67✔
96
 * ```
67✔
97
 */
67✔
98
export function wrap(min: number, max: number, value: number) {
67✔
99
  if (value < min) {
37✔
100
    return max;
11✔
101
  }
11✔
102
  if (value > max) {
37✔
103
    return min;
3✔
104
  }
3✔
105

23✔
106
  return value;
23✔
107
}
23✔
108

67✔
109
export function isDefined<T = unknown>(value: T) {
67✔
110
  return value !== undefined;
2,340✔
111
}
2,340✔
112

67✔
113
export type IterNodesOptions<T = Node> = {
67✔
114
  show?: keyof typeof NodeFilter;
67✔
115
  filter?: (node: T) => boolean;
67✔
116
};
67✔
117

67✔
118
function createNodeFilter<T extends Node>(predicate: (node: T) => boolean) {
989✔
119
  return {
989✔
120
    acceptNode: (node: T): number =>
989✔
121
      !predicate || predicate(node)
10,111✔
122
        ? NodeFilter.FILTER_ACCEPT
3,994✔
123
        : NodeFilter.FILTER_SKIP,
6,117✔
124
  };
989✔
125
}
989✔
126

67✔
127
export function* iterNodes<T extends Node>(
67✔
128
  root: Node,
989✔
129
  options?: IterNodesOptions<T>
989✔
130
): Generator<T> {
989✔
131
  if (!isDefined(globalThis.document)) {
989!
UNCOV
132
    return;
×
133
  }
×
134

989✔
135
  const whatToShow = options?.show
989✔
136
    ? NodeFilter[options.show]
989!
NEW
UNCOV
137
    : NodeFilter.SHOW_ALL;
×
138

989✔
139
  const nodeFilter = options?.filter
989✔
140
    ? createNodeFilter(options.filter)
989!
NEW
UNCOV
141
    : undefined;
×
142

989✔
143
  const treeWalker = document.createTreeWalker(root, whatToShow, nodeFilter);
989✔
144

989✔
145
  while (treeWalker.nextNode()) {
989✔
146
    yield treeWalker.currentNode as T;
3,994✔
147
  }
3,994✔
148
}
989✔
149

67✔
150
export function getRoot(
67✔
151
  element: Element,
799✔
152
  options?: GetRootNodeOptions
799✔
153
): Document | ShadowRoot {
799✔
154
  return element.getRootNode(options) as Document | ShadowRoot;
799✔
155
}
799✔
156

67✔
157
export function getElementByIdFromRoot(root: HTMLElement, id: string) {
67✔
158
  return getRoot(root).getElementById(id);
192✔
159
}
192✔
160

67✔
161
export function isElement(node: unknown): node is Element {
67✔
162
  return node instanceof Node && node.nodeType === Node.ELEMENT_NODE;
13,325✔
163
}
13,325✔
164

67✔
165
export function getElementsFromEventPath<T extends Element>(event: Event) {
67✔
166
  return event.composedPath().filter((item) => isElement(item)) as T[];
1,294✔
167
}
1,294✔
168

67✔
169
export function findElementFromEventPath<T extends Element>(
67✔
170
  predicate: string | ((element: Element) => boolean),
1,294✔
171
  event: Event
1,294✔
172
) {
1,294✔
173
  const func = isString(predicate)
1,294✔
174
    ? (e: Element) => e.matches(predicate)
350✔
175
    : (e: Element) => predicate(e);
944✔
176

1,294✔
177
  return getElementsFromEventPath(event).find(func) as T | undefined;
1,294✔
178
}
1,294✔
179

67✔
180
export function groupBy<T>(array: T[], key: keyof T | ((item: T) => any)) {
67✔
181
  const result: Record<string, T[]> = {};
131✔
182
  const _get = isFunction(key) ? key : (item: T) => item[key];
131!
183

131✔
184
  for (const item of array) {
131✔
185
    const category = _get(item);
730✔
186
    const group = result[category];
730✔
187

730✔
188
    if (Array.isArray(group)) {
730✔
189
      group.push(item);
476✔
190
    } else {
730✔
191
      result[category] = [item];
254✔
192
    }
254✔
193
  }
730✔
194

131✔
195
  return result;
131✔
196
}
131✔
197

67✔
198
export function first<T>(arr: T[]) {
67✔
199
  return arr.at(0) as T;
12,660✔
200
}
12,660✔
201

67✔
202
export function last<T>(arr: T[]) {
67✔
203
  return arr.at(-1) as T;
10,212✔
204
}
10,212✔
205

67✔
206
export function modulo(n: number, d: number) {
67✔
207
  return ((n % d) + d) % d;
1,108✔
208
}
1,108✔
209

67✔
210
/**
67✔
211
 * Creates an array of `n` elements from a given iterator.
67✔
212
 *
67✔
213
 */
67✔
214
export function take<T>(iterable: IterableIterator<T>, n: number) {
67✔
215
  const result: T[] = [];
453✔
216
  let i = 0;
453✔
217
  let current = iterable.next();
453✔
218

453✔
219
  while (i < n && !current.done) {
453✔
220
    result.push(current.value);
3,171✔
221
    current = iterable.next();
3,171✔
222
    i++;
3,171✔
223
  }
3,171✔
224

453✔
225
  return result;
453✔
226
}
453✔
227

67✔
228
/**
67✔
229
 * Splits an array into chunks of length `size` and returns a generator
67✔
230
 * yielding each chunk.
67✔
231
 * The last chunk may contain less than `size` elements.
67✔
232
 *
67✔
233
 * @example
67✔
234
 * ```typescript
67✔
235
 * const arr = [0,1,2,3,4,5,6,7,8,9];
67✔
236
 *
67✔
237
 * Array.from(chunk(arr, 2)) // [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
67✔
238
 * Array.from(chunk(arr, 3)) // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
67✔
239
 * Array.from(chunk([], 3)) // []
67✔
240
 * Array.from(chunk(arr, -3)) // Error
67✔
241
 * ```
67✔
242
 */
67✔
243
export function* chunk<T>(arr: T[], size: number) {
67✔
244
  if (size < 1) {
529!
245
    throw new Error('size must be an integer >= 1');
×
246
  }
×
247
  for (let i = 0; i < arr.length; i += size) {
529✔
248
    yield arr.slice(i, i + size);
3,059✔
249
  }
3,059✔
250
}
529✔
251

67✔
252
export function splitToWords(text: string) {
67✔
253
  const input = text.replaceAll(/[^a-zA-Z0-9\s-_]/g, '');
5,228✔
254
  if (/[\s-_]+/.test(input)) return input.split(/[\s-_]+/);
5,228!
255
  return input.split(/(?=[A-Z])+/);
5,228✔
256
}
5,228✔
257

67✔
258
export function toKebabCase(text: string): string {
67✔
259
  const input = text.trim();
5,228✔
260
  return splitToWords(input).join('-').toLocaleLowerCase();
5,228✔
261
}
5,228✔
262

67✔
263
export function isFunction(value: unknown): value is CallableFunction {
67✔
264
  return typeof value === 'function';
584✔
265
}
584✔
266

67✔
267
export function isString(value: unknown): value is string {
67✔
268
  return typeof value === 'string';
3,591✔
269
}
3,591✔
270

67✔
271
export function isObject(value: unknown): value is object {
67✔
272
  return value != null && typeof value === 'object';
2,269✔
273
}
2,269✔
274

67✔
275
export function isEventListenerObject(x: unknown): x is EventListenerObject {
67✔
276
  return isObject(x) && 'handleEvent' in x;
28✔
277
}
28✔
278

67✔
279
export function addWeakEventListener(
67✔
280
  element: Element,
116✔
281
  event: string,
116✔
282
  listener: EventListenerOrEventListenerObject,
116✔
283
  options?: AddEventListenerOptions | boolean
116✔
284
): void {
116✔
285
  const weakRef = new WeakRef(listener);
116✔
286
  const wrapped = (evt: Event) => {
116✔
287
    const handler = weakRef.deref();
28✔
288

28✔
289
    return isEventListenerObject(handler)
28✔
290
      ? handler.handleEvent(evt)
28!
291
      : handler?.(evt);
×
292
  };
28✔
293

116✔
294
  element.addEventListener(event, wrapped, options);
116✔
295
}
116✔
296

67✔
297
/**
67✔
298
 * Returns whether a given collection is empty.
67✔
299
 */
67✔
300
export function isEmpty<T, U extends object>(
67✔
301
  x: ArrayLike<T> | Set<T> | Map<U, T>
13,469✔
302
): boolean {
13,469✔
303
  return 'length' in x ? x.length < 1 : x.size < 1;
13,469✔
304
}
13,469✔
305

67✔
306
export function asArray<T>(value?: T | T[]): T[] {
67✔
307
  if (!isDefined(value)) return [];
781✔
308
  return Array.isArray(value) ? value : [value];
781✔
309
}
781✔
310

67✔
311
export function partition<T>(
67✔
312
  array: T[],
91✔
313
  isTruthy: (value: T) => boolean
91✔
314
): [truthy: T[], falsy: T[]] {
91✔
315
  const truthy: T[] = [];
91✔
316
  const falsy: T[] = [];
91✔
317

91✔
318
  for (const item of array) {
91✔
319
    (isTruthy(item) ? truthy : falsy).push(item);
373✔
320
  }
373✔
321

91✔
322
  return [truthy, falsy];
91✔
323
}
91✔
324

67✔
325
/** Returns the center x/y coordinate of a given element. */
67✔
326
export function getCenterPoint(element: Element) {
67✔
327
  const { left, top, width, height } = element.getBoundingClientRect();
20✔
328

20✔
329
  return {
20✔
330
    x: left + width * 0.5,
20✔
331
    y: top + height * 0.5,
20✔
332
  };
20✔
333
}
20✔
334

67✔
335
export function roundByDPR(value: number): number {
67✔
336
  const dpr = globalThis.devicePixelRatio || 1;
1,270!
337
  return Math.round(value * dpr) / dpr;
1,270✔
338
}
1,270✔
339

67✔
340
export function scrollIntoView(
67✔
341
  element?: HTMLElement,
82✔
342
  config?: ScrollIntoViewOptions
82✔
343
): void {
82✔
344
  if (!element) {
82!
345
    return;
×
346
  }
×
347

82✔
348
  element.scrollIntoView(
82✔
349
    Object.assign(
82✔
350
      {
82✔
351
        behavior: 'auto',
82✔
352
        block: 'nearest',
82✔
353
        inline: 'nearest',
82✔
354
      },
82✔
355
      config
82✔
356
    )
82✔
357
  );
82✔
358
}
82✔
359

67✔
360
export function isRegExp(value: unknown): value is RegExp {
67✔
361
  return value != null && value.constructor === RegExp;
1,062✔
362
}
1,062✔
363

67✔
364
export function equal<T>(a: unknown, b: T, visited = new WeakSet()): boolean {
67✔
365
  // Early return
1,211✔
366
  if (Object.is(a, b)) {
1,211✔
367
    return true;
38✔
368
  }
38✔
369

1,173✔
370
  if (isObject(a) && isObject(b)) {
1,211✔
371
    if (a.constructor !== b.constructor) {
1,068✔
372
      return false;
1✔
373
    }
1✔
374

1,067✔
375
    // Circular references
1,067✔
376
    if (visited.has(a) && visited.has(b)) {
1,068✔
377
      return true;
8✔
378
    }
8✔
379

1,059✔
380
    visited.add(a);
1,059✔
381
    visited.add(b);
1,059✔
382

1,059✔
383
    // RegExp
1,059✔
384
    if (isRegExp(a) && isRegExp(b)) {
1,068✔
385
      return a.source === b.source && a.flags === b.flags;
3✔
386
    }
3✔
387

1,056✔
388
    // Maps
1,056✔
389
    if (a instanceof Map && b instanceof Map) {
1,068✔
390
      if (a.size !== b.size) {
12✔
391
        return false;
4✔
392
      }
4✔
393
      for (const [keyA, valueA] of a.entries()) {
12✔
394
        let found = false;
7✔
395
        for (const [keyB, valueB] of b.entries()) {
7✔
396
          if (equal(keyA, keyB, visited) && equal(valueA, valueB, visited)) {
9✔
397
            found = true;
5✔
398
            break;
5✔
399
          }
5✔
400
        }
9✔
401
        if (!found) {
7✔
402
          return false;
2✔
403
        }
2✔
404
      }
7✔
405
      return true;
6✔
406
    }
6✔
407

1,044✔
408
    // Sets
1,044✔
409
    if (a instanceof Set && b instanceof Set) {
1,068✔
410
      if (a.size !== b.size) {
12✔
411
        return false;
5✔
412
      }
5✔
413
      for (const valueA of a) {
7✔
414
        let found = false;
7✔
415
        for (const valueB of b) {
7✔
416
          if (equal(valueA, valueB, visited)) {
9✔
417
            found = true;
6✔
418
            break;
6✔
419
          }
6✔
420
        }
9✔
421
        if (!found) {
7✔
422
          return false;
1✔
423
        }
1✔
424
      }
7✔
425
      return true;
6✔
426
    }
6✔
427

1,032✔
428
    // Arrays
1,032✔
429
    if (Array.isArray(a) && Array.isArray(b)) {
1,068✔
430
      const length = a.length;
13✔
431
      if (length !== b.length) {
13✔
432
        return false;
4✔
433
      }
4✔
434
      for (let i = 0; i < length; i++) {
13✔
435
        if (!equal(a[i], b[i], visited)) {
13✔
436
          return false;
2✔
437
        }
2✔
438
      }
13✔
439
      return true;
7✔
440
    }
7✔
441

1,019✔
442
    // toPrimitive
1,019✔
443
    if (a.valueOf !== Object.prototype.valueOf) {
1,068✔
444
      return a.valueOf() === b.valueOf();
4✔
445
    }
4✔
446
    // Strings based
1,015✔
447
    if (a.toString !== Object.prototype.toString) {
1,068✔
448
      return a.toString() === b.toString();
2✔
449
    }
2✔
450

1,013✔
451
    const aKeys = Object.keys(a);
1,013✔
452
    const bKeys = Object.keys(b);
1,013✔
453
    if (aKeys.length !== bKeys.length) {
1,068✔
454
      return false;
900✔
455
    }
900✔
456

113✔
457
    for (const key of aKeys) {
200✔
458
      if (!Object.prototype.hasOwnProperty.call(b, key)) {
303✔
459
        return false;
1✔
460
      }
1✔
461
    }
303✔
462

112✔
463
    for (const key of aKeys) {
200✔
464
      if (!equal(a[key as keyof typeof a], b[key as keyof typeof b], visited)) {
121✔
465
        return false;
96✔
466
      }
96✔
467
    }
121✔
468

16✔
469
    visited.delete(a);
16✔
470
    visited.delete(b);
16✔
471

16✔
472
    return true;
16✔
473
  }
16✔
474

105✔
475
  return false;
105✔
476
}
105✔
477

67✔
478
/** Required utility type for specific props */
67✔
479
export type RequiredProps<T, K extends keyof T> = T & {
67✔
480
  [P in K]-?: T[P];
67✔
481
};
67✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc