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

IgniteUI / igniteui-webcomponents / 14702825363

28 Apr 2025 07:42AM UTC coverage: 98.263% (-0.02%) from 98.279%
14702825363

Pull #1352

github

web-flow
Merge 6e058e971 into 0f89d7575
Pull Request #1352: Refactor Tab component

4590 of 4823 branches covered (95.17%)

Branch coverage included in aggregate %.

582 of 592 new or added lines in 5 files covered. (98.31%)

1 existing line in 1 file now uncovered.

29414 of 29782 relevant lines covered (98.76%)

453.65 hits per line

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

96.94
/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,794✔
7
    .filter((key) => partNameInfo[key])
50,794✔
8
    .join(' ');
50,794✔
9
};
50,794✔
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,967✔
49
}
2,967✔
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,588✔
82
  return Number.isNaN(parsed) ? fallback : parsed;
1,588✔
83
}
1,588✔
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;
8,185✔
111
}
8,185✔
112

67✔
113
export function* iterNodes<T = Node>(
67✔
114
  root: Node,
6,834✔
115
  whatToShow?: keyof typeof NodeFilter,
6,834✔
116
  filter?: (node: T) => boolean
6,834✔
117
): Generator<T> {
6,834✔
118
  if (!isDefined(globalThis.document)) {
6,834!
119
    return;
×
120
  }
×
121

6,834✔
122
  const iter = globalThis.document.createTreeWalker(
6,834✔
123
    root,
6,834✔
124
    NodeFilter[whatToShow ?? 'SHOW_ALL']
6,834!
125
  );
6,834✔
126

6,834✔
127
  let node = iter.nextNode() as T;
6,834✔
128

6,834✔
129
  while (node) {
6,834✔
130
    if (filter) {
39,558✔
131
      if (filter(node)) {
39,558✔
132
        yield node;
3,966✔
133
      }
3,966✔
134
    } else {
39,558!
135
      yield node;
×
136
    }
×
137

39,558✔
138
    if (isElement(node) && node.shadowRoot && node.shadowRoot.mode === 'open') {
39,558✔
139
      yield* iterNodes(node.shadowRoot, whatToShow, filter);
5,845✔
140
    }
5,845✔
141

39,558✔
142
    node = iter.nextNode() as T;
39,558✔
143
  }
39,558✔
144
}
6,834✔
145

67✔
146
export function getRoot(
67✔
147
  element: Element,
251✔
148
  options?: GetRootNodeOptions
251✔
149
): Document | ShadowRoot {
251✔
150
  return element.getRootNode(options) as Document | ShadowRoot;
251✔
151
}
251✔
152

67✔
153
export function getElementByIdFromRoot(root: HTMLElement, id: string) {
67✔
154
  return getRoot(root).getElementById(id);
192✔
155
}
192✔
156

67✔
157
export function isElement(node: unknown): node is Element {
67✔
158
  return node instanceof Node && node.nodeType === Node.ELEMENT_NODE;
52,883✔
159
}
52,883✔
160

67✔
161
export function getElementsFromEventPath<T extends Element>(event: Event) {
67✔
162
  return event.composedPath().filter((item) => isElement(item)) as T[];
1,294✔
163
}
1,294✔
164

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

1,294✔
173
  return getElementsFromEventPath(event).find(func) as T | undefined;
1,294✔
174
}
1,294✔
175

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

131✔
180
  for (const item of array) {
131✔
181
    const category = _get(item);
730✔
182
    const group = result[category];
730✔
183

730✔
184
    if (Array.isArray(group)) {
730✔
185
      group.push(item);
476✔
186
    } else {
730✔
187
      result[category] = [item];
254✔
188
    }
254✔
189
  }
730✔
190

131✔
191
  return result;
131✔
192
}
131✔
193

67✔
194
export function first<T>(arr: T[]) {
67✔
195
  return arr.at(0) as T;
12,646✔
196
}
12,646✔
197

67✔
198
export function last<T>(arr: T[]) {
67✔
199
  return arr.at(-1) as T;
10,212✔
200
}
10,212✔
201

67✔
202
export function modulo(n: number, d: number) {
67✔
203
  return ((n % d) + d) % d;
1,108✔
204
}
1,108✔
205

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

453✔
215
  while (i < n && !current.done) {
453✔
216
    result.push(current.value);
3,171✔
217
    current = iterable.next();
3,171✔
218
    i++;
3,171✔
219
  }
3,171✔
220

453✔
221
  return result;
453✔
222
}
453✔
223

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

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

67✔
254
export function toKebabCase(text: string): string {
67✔
255
  const input = text.trim();
5,228✔
256
  return splitToWords(input).join('-').toLocaleLowerCase();
5,228✔
257
}
5,228✔
258

67✔
259
export function isFunction(value: unknown): value is CallableFunction {
67✔
260
  return typeof value === 'function';
583✔
261
}
583✔
262

67✔
263
export function isString(value: unknown): value is string {
67✔
264
  return typeof value === 'string';
3,591✔
265
}
3,591✔
266

67✔
267
export function isObject(value: unknown): value is object {
67✔
268
  return value != null && typeof value === 'object';
2,269✔
269
}
2,269✔
270

67✔
271
export function isEventListenerObject(x: unknown): x is EventListenerObject {
67✔
272
  return isObject(x) && 'handleEvent' in x;
28✔
273
}
28✔
274

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

28✔
285
    return isEventListenerObject(handler)
28✔
286
      ? handler.handleEvent(evt)
28!
287
      : handler?.(evt);
×
288
  };
28✔
289

116✔
290
  element.addEventListener(event, wrapped, options);
116✔
291
}
116✔
292

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

67✔
302
export function asArray<T>(value?: T | T[]): T[] {
67✔
303
  if (!isDefined(value)) return [];
781✔
304
  return Array.isArray(value) ? value : [value];
781✔
305
}
781✔
306

67✔
307
export function partition<T>(
67✔
308
  array: T[],
91✔
309
  isTruthy: (value: T) => boolean
91✔
310
): [truthy: T[], falsy: T[]] {
91✔
311
  const truthy: T[] = [];
91✔
312
  const falsy: T[] = [];
91✔
313

91✔
314
  for (const item of array) {
91✔
315
    (isTruthy(item) ? truthy : falsy).push(item);
373✔
316
  }
373✔
317

91✔
318
  return [truthy, falsy];
91✔
319
}
91✔
320

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

20✔
325
  return {
20✔
326
    x: left + width * 0.5,
20✔
327
    y: top + height * 0.5,
20✔
328
  };
20✔
329
}
20✔
330

67✔
331
export function roundByDPR(value: number): number {
67✔
332
  const dpr = globalThis.devicePixelRatio || 1;
1,258!
333
  return Math.round(value * dpr) / dpr;
1,258✔
334
}
1,258✔
335

67✔
336
export function scrollIntoView(
67✔
337
  element?: HTMLElement,
82✔
338
  config?: ScrollIntoViewOptions
82✔
339
): void {
82✔
340
  if (!element) {
82!
NEW
341
    return;
×
NEW
342
  }
×
343

82✔
344
  element.scrollIntoView(
82✔
345
    Object.assign(
82✔
346
      {
82✔
347
        behavior: 'auto',
82✔
348
        block: 'nearest',
82✔
349
        inline: 'nearest',
82✔
350
      },
82✔
351
      config
82✔
352
    )
82✔
353
  );
82✔
354
}
82✔
355

67✔
356
export function isRegExp(value: unknown): value is RegExp {
67✔
357
  return value != null && value.constructor === RegExp;
1,062✔
358
}
1,062✔
359

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

1,173✔
366
  if (isObject(a) && isObject(b)) {
1,211✔
367
    if (a.constructor !== b.constructor) {
1,068✔
368
      return false;
1✔
369
    }
1✔
370

1,067✔
371
    // Circular references
1,067✔
372
    if (visited.has(a) && visited.has(b)) {
1,068✔
373
      return true;
8✔
374
    }
8✔
375

1,059✔
376
    visited.add(a);
1,059✔
377
    visited.add(b);
1,059✔
378

1,059✔
379
    // RegExp
1,059✔
380
    if (isRegExp(a) && isRegExp(b)) {
1,068✔
381
      return a.source === b.source && a.flags === b.flags;
3✔
382
    }
3✔
383

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

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

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

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

1,013✔
447
    const aKeys = Object.keys(a);
1,013✔
448
    const bKeys = Object.keys(b);
1,013✔
449
    if (aKeys.length !== bKeys.length) {
1,068✔
450
      return false;
900✔
451
    }
900✔
452

113✔
453
    for (const key of aKeys) {
200✔
454
      if (!Object.prototype.hasOwnProperty.call(b, key)) {
303✔
455
        return false;
1✔
456
      }
1✔
457
    }
303✔
458

112✔
459
    for (const key of aKeys) {
200✔
460
      if (!equal(a[key as keyof typeof a], b[key as keyof typeof b], visited)) {
121✔
461
        return false;
96✔
462
      }
96✔
463
    }
121✔
464

16✔
465
    visited.delete(a);
16✔
466
    visited.delete(b);
16✔
467

16✔
468
    return true;
16✔
469
  }
16✔
470

105✔
471
  return false;
105✔
472
}
105✔
473

67✔
474
/** Required utility type for specific props */
67✔
475
export type RequiredProps<T, K extends keyof T> = T & {
67✔
476
  [P in K]-?: T[P];
67✔
477
};
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