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

IgniteUI / igniteui-webcomponents / 15186545283

22 May 2025 12:27PM UTC coverage: 98.294%. Remained the same
15186545283

Pull #1689

github

web-flow
Merge 064c5e4c5 into 0a65b7763
Pull Request #1689: refactor: Simplify iterNodes implementation

4604 of 4833 branches covered (95.26%)

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.

29452 of 29814 relevant lines covered (98.79%)

430.98 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 {
68✔
2
  readonly [name: string]: string | boolean | number;
68✔
3
}
68✔
4

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

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

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

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

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

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

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

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

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

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

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

68✔
85
/**
68✔
86
 * Returns the value wrapped between the min and max bounds.
68✔
87
 *
68✔
88
 * If the value is greater than max, returns the min and vice-versa.
68✔
89
 * If the value is between the bounds, it is returned unchanged.
68✔
90
 *
68✔
91
 * @example
68✔
92
 * ```typescript
68✔
93
 * wrap(1, 4, 2); // 2
68✔
94
 * wrap(1, 4, 5); // 1
68✔
95
 * wrap(1, 4, -1); // 4
68✔
96
 * ```
68✔
97
 */
68✔
98
export function wrap(min: number, max: number, value: number) {
68✔
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

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

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

68✔
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

68✔
127
export function* iterNodes<T extends Node>(
68✔
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

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

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

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

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

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

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

68✔
180
export function groupBy<T>(array: T[], key: keyof T | ((item: T) => any)) {
68✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

68✔
279
export function addWeakEventListener(
68✔
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

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

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

68✔
311
export function partition<T>(
68✔
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

68✔
325
/** Returns the center x/y coordinate of a given element. */
68✔
326
export function getCenterPoint(element: Element) {
68✔
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

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

68✔
340
export function scrollIntoView(
68✔
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

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

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

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

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

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

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

1,111✔
388
    // Maps
1,111✔
389
    if (a instanceof Map && b instanceof Map) {
1,123✔
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,099✔
408
    // Sets
1,099✔
409
    if (a instanceof Set && b instanceof Set) {
1,123✔
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,087✔
428
    // Arrays
1,087✔
429
    if (Array.isArray(a) && Array.isArray(b)) {
1,123✔
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,074✔
442
    // toPrimitive
1,074✔
443
    if (a.valueOf !== Object.prototype.valueOf) {
1,123✔
444
      return a.valueOf() === b.valueOf();
4✔
445
    }
4✔
446
    // Strings based
1,070✔
447
    if (a.toString !== Object.prototype.toString) {
1,123✔
448
      return a.toString() === b.toString();
29✔
449
    }
29✔
450

1,041✔
451
    const aKeys = Object.keys(a);
1,041✔
452
    const bKeys = Object.keys(b);
1,041✔
453
    if (aKeys.length !== bKeys.length) {
1,096✔
454
      return false;
928✔
455
    }
928✔
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

68✔
478
/** Required utility type for specific props */
68✔
479
export type RequiredProps<T, K extends keyof T> = T & {
68✔
480
  [P in K]-?: T[P];
68✔
481
};
68✔
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

© 2026 Coveralls, Inc