Skip to content

Commit 1d5daa7

Browse files
authored
feat: Add multi-label selection and Table metadata extraction (#182)
1 parent c09d82c commit 1d5daa7

File tree

6 files changed

+580
-15
lines changed

6 files changed

+580
-15
lines changed

src/internal/analytics-metadata/__tests__/labels-utils.test.tsx

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,280 @@ describe('processLabel', () => {
306306
expect(processLabel(target, '.redirect-label-class-four')).toEqual('content inside header');
307307
expect(processLabel(target, '.redirect-label-class-five')).toEqual('');
308308
});
309+
310+
describe('with selectionMode parameter', () => {
311+
test('returns single label when selectionMode is "single" (default)', () => {
312+
const { container } = render(
313+
<div>
314+
<div id="target">
315+
<div className="item">Item 1</div>
316+
<div className="item">Item 2</div>
317+
<div className="item">Item 3</div>
318+
</div>
319+
</div>
320+
);
321+
const target = container.querySelector('#target') as HTMLElement;
322+
323+
expect(processLabel(target, '.item', 'single')).toEqual('Item 1');
324+
expect(processLabel(target, '.item')).toEqual('Item 1'); // default is 'single'
325+
});
326+
327+
test('returns array of labels when selectionMode is "multi"', () => {
328+
const { container } = render(
329+
<div>
330+
<div id="target">
331+
<div className="item">Item 1</div>
332+
<div className="item">Item 2</div>
333+
<div className="item">Item 3</div>
334+
</div>
335+
</div>
336+
);
337+
const target = container.querySelector('#target') as HTMLElement;
338+
339+
expect(processLabel(target, '.item', 'multi')).toEqual(['Item 1', 'Item 2', 'Item 3']);
340+
});
341+
342+
test('returns empty array when no elements match in multi mode', () => {
343+
const { container } = render(
344+
<div>
345+
<div id="target">
346+
<div className="item">Item 1</div>
347+
</div>
348+
</div>
349+
);
350+
const target = container.querySelector('#target') as HTMLElement;
351+
352+
expect(processLabel(target, '.nonexistent', 'multi')).toEqual([]);
353+
});
354+
355+
test('filters out empty labels in multi mode', () => {
356+
const { container } = render(
357+
<div>
358+
<div id="target">
359+
<div className="item">Item 1</div>
360+
<div className="item"></div>
361+
<div className="item">Item 3</div>
362+
</div>
363+
</div>
364+
);
365+
const target = container.querySelector('#target') as HTMLElement;
366+
367+
expect(processLabel(target, '.item', 'multi')).toEqual(['Item 1', 'Item 3']);
368+
});
369+
370+
test('handles nested label resolution in multi mode', () => {
371+
const { container } = render(
372+
<div>
373+
<div id="target">
374+
<div className="item" {...getAnalyticsLabelAttribute('.nested')}>
375+
<span className="nested">Nested 1</span>
376+
</div>
377+
<div className="item" {...getAnalyticsLabelAttribute('.nested')}>
378+
<span className="nested">Nested 2</span>
379+
</div>
380+
<div className="item">Direct 3</div>
381+
</div>
382+
</div>
383+
);
384+
const target = container.querySelector('#target') as HTMLElement;
385+
386+
expect(processLabel(target, '.item', 'multi')).toEqual(['Nested 1', 'Nested 2', 'Direct 3']);
387+
});
388+
389+
test('flattens nested arrays from recursive processing in multi mode', () => {
390+
const { container } = render(
391+
<div>
392+
<div id="target">
393+
<div className="item" {...getAnalyticsLabelAttribute('.nested')}>
394+
<span className="nested">Nested 1</span>
395+
<span className="nested">Nested 2</span>
396+
</div>
397+
<div className="item">Direct</div>
398+
</div>
399+
</div>
400+
);
401+
const target = container.querySelector('#target') as HTMLElement;
402+
403+
const result = processLabel(target, '.item', 'multi');
404+
expect(Array.isArray(result)).toBe(true);
405+
expect((result as string[]).every(item => typeof item === 'string')).toBe(true);
406+
});
407+
408+
test('handles aria-label in multi mode', () => {
409+
const { container } = render(
410+
<div>
411+
<div id="target">
412+
<div className="item" aria-label="Label 1">
413+
Content 1
414+
</div>
415+
<div className="item" aria-label="Label 2">
416+
Content 2
417+
</div>
418+
<div className="item">Content 3</div>
419+
</div>
420+
</div>
421+
);
422+
const target = container.querySelector('#target') as HTMLElement;
423+
424+
expect(processLabel(target, '.item', 'multi')).toEqual(['Label 1', 'Label 2', 'Content 3']);
425+
});
426+
427+
test('works with LabelIdentifier object in multi mode', () => {
428+
const { container } = render(
429+
<div>
430+
<div id="target">
431+
<div className="item">Item 1</div>
432+
<div className="item">Item 2</div>
433+
</div>
434+
</div>
435+
);
436+
const target = container.querySelector('#target') as HTMLElement;
437+
438+
expect(processLabel(target, { selector: '.item' }, 'multi')).toEqual(['Item 1', 'Item 2']);
439+
});
440+
441+
test('returns empty array when selector is undefined in multi mode', () => {
442+
const { container } = render(
443+
<div>
444+
<div id="target">
445+
<div className="item">Item 1</div>
446+
</div>
447+
</div>
448+
);
449+
const target = container.querySelector('#target') as HTMLElement;
450+
451+
expect(processLabel(target, { selector: undefined }, 'multi')).toEqual([]);
452+
});
453+
454+
test('returns empty array when labelIdentifier is null in multi mode', () => {
455+
const { container } = render(
456+
<div>
457+
<div id="target">
458+
<div className="item">Item 1</div>
459+
</div>
460+
</div>
461+
);
462+
const target = container.querySelector('#target') as HTMLElement;
463+
464+
expect(processLabel(target, null, 'multi')).toEqual([]);
465+
});
466+
467+
test('returns empty array when node is null in multi mode', () => {
468+
expect(processLabel(null, '.item', 'multi')).toEqual([]);
469+
});
470+
471+
test('returns first non-empty labels when selector is an array in multi mode', () => {
472+
const { container } = render(
473+
<div>
474+
<div id="target">
475+
<div className="wrong-class">Wrong 1</div>
476+
<div className="item">Item 1</div>
477+
<div className="item">Item 2</div>
478+
<div className="other">Other 1</div>
479+
</div>
480+
</div>
481+
);
482+
const target = container.querySelector('#target') as HTMLElement;
483+
484+
// Should return first successful selector's results
485+
expect(processLabel(target, { selector: ['.nonexistent', '.item', '.other'] }, 'multi')).toEqual([
486+
'Item 1',
487+
'Item 2',
488+
]);
489+
});
490+
491+
test('returns empty array when all selectors in array fail in multi mode', () => {
492+
const { container } = render(
493+
<div>
494+
<div id="target">
495+
<div className="item">Item 1</div>
496+
</div>
497+
</div>
498+
);
499+
const target = container.querySelector('#target') as HTMLElement;
500+
501+
expect(processLabel(target, { selector: ['.nonexistent1', '.nonexistent2', '.nonexistent3'] }, 'multi')).toEqual(
502+
[]
503+
);
504+
});
505+
506+
test('respects rootSelector property in multi mode', () => {
507+
const { container } = render(
508+
<div className="root-class">
509+
<div className="item">Root Item 1</div>
510+
<div className="item">Root Item 2</div>
511+
<div id="target">
512+
<div className="item">Inner Item 1</div>
513+
<div className="item">Inner Item 2</div>
514+
</div>
515+
</div>
516+
);
517+
const target = container.querySelector('#target') as HTMLElement;
518+
519+
expect(processLabel(target, { selector: '.item', rootSelector: '.root-class' }, 'multi')).toEqual([
520+
'Root Item 1',
521+
'Root Item 2',
522+
'Inner Item 1',
523+
'Inner Item 2',
524+
]);
525+
});
526+
527+
test('respects root="component" property in multi mode', () => {
528+
const { container } = render(
529+
<>
530+
<div {...getAnalyticsMetadataAttribute({ component: { name: 'ComponentName' } })}>
531+
<div className="item">Component Item 1</div>
532+
<div className="item">Component Item 2</div>
533+
<div id="target">
534+
<div className="item">Inner Item 1</div>
535+
<div className="item">Inner Item 2</div>
536+
</div>
537+
</div>
538+
</>
539+
);
540+
const target = container.querySelector('#target') as HTMLElement;
541+
542+
// querySelectorAll finds ALL descendants from the component root
543+
expect(processLabel(target, { selector: '.item', root: 'component' }, 'multi')).toEqual([
544+
'Component Item 1',
545+
'Component Item 2',
546+
'Inner Item 1',
547+
'Inner Item 2',
548+
]);
549+
});
550+
551+
test('respects root="body" property in multi mode', () => {
552+
const { container } = render(
553+
<>
554+
<div className="outer-item">Outer Item 1</div>
555+
<div className="outer-item">Outer Item 2</div>
556+
<div id="target">
557+
<div className="outer-item">Inner Item 1</div>
558+
</div>
559+
</>
560+
);
561+
const target = container.querySelector('#target') as HTMLElement;
562+
563+
expect(processLabel(target, { selector: '.outer-item', root: 'body' }, 'multi')).toEqual([
564+
'Outer Item 1',
565+
'Outer Item 2',
566+
'Inner Item 1',
567+
]);
568+
});
569+
});
570+
571+
describe('edge cases with array selectors in single mode', () => {
572+
test('returns empty string when all selectors in array fail', () => {
573+
const { container } = render(
574+
<div>
575+
<div id="target">
576+
<div className="item">Item 1</div>
577+
</div>
578+
</div>
579+
);
580+
const target = container.querySelector('#target') as HTMLElement;
581+
582+
expect(processLabel(target, { selector: ['.nonexistent1', '.nonexistent2'] })).toEqual('');
583+
});
584+
});
309585
});

0 commit comments

Comments
 (0)