Skip to content

Commit ba1e797

Browse files
fralongoFrancesco Longo
andauthored
feat: Allow to specify a root class name as a label identifier in ana… (#97)
Co-authored-by: Francesco Longo <[email protected]>
1 parent d5a6726 commit ba1e797

File tree

9 files changed

+128
-14
lines changed

9 files changed

+128
-14
lines changed

src/internal/analytics-metadata/__tests__/components.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const ComponentThree = () => (
5252
position: '2',
5353
columnLabel: { selector: '.invalid-selector', root: 'self' },
5454
anotherLabel: { root: 'self' },
55+
yetAnotherLabel: { rootSelector: '.root-class-name' },
5556
},
5657
},
5758
})}

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

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import React from 'react';
55
import { render } from '@testing-library/react';
66
import { activateAnalyticsMetadata, getAnalyticsMetadataAttribute, METADATA_ATTRIBUTE } from '../attributes';
7-
import { findLogicalParent, isNodeComponent, findComponentUp } from '../dom-utils';
7+
import { findLogicalParent, isNodeComponent, findComponentUp, findSelectorUp } from '../dom-utils';
88

99
beforeAll(() => {
1010
activateAnalyticsMetadata(true);
@@ -18,7 +18,7 @@ describe('findLogicalParent', () => {
1818
</div>
1919
);
2020
const child = container.querySelector('#child');
21-
expect(findLogicalParent(child as HTMLElement)?.id).toEqual('parent');
21+
expect(findLogicalParent(child as HTMLElement)!.id).toEqual('parent');
2222
});
2323
test('returns null when child does not exist', () => {
2424
const { container } = render(
@@ -88,7 +88,7 @@ describe('findComponentUp', () => {
8888
<div id="target-element"></div>
8989
</div>
9090
);
91-
expect(findComponentUp(container.querySelector('#target-element'))?.id).toBe('component-element');
91+
expect(findComponentUp(container.querySelector('#target-element'))!.id).toBe('component-element');
9292
});
9393
test('returns parent component element with portals', () => {
9494
const { container } = render(
@@ -101,7 +101,7 @@ describe('findComponentUp', () => {
101101
</div>
102102
</div>
103103
);
104-
expect(findComponentUp(container.querySelector('#target-element'))?.id).toBe('component-element');
104+
expect(findComponentUp(container.querySelector('#target-element'))!.id).toBe('component-element');
105105
});
106106
test('returns null when element has no parent component', () => {
107107
const { container } = render(
@@ -112,3 +112,44 @@ describe('findComponentUp', () => {
112112
expect(findComponentUp(container.querySelector('#target-element'))).toBeNull();
113113
});
114114
});
115+
116+
describe('findSelectorUp', () => {
117+
test('returns null when the node is null or the className is invalid', () => {
118+
expect(findSelectorUp(null, 'abcd')).toBeNull();
119+
const { container } = render(
120+
<div id="root-element">
121+
<div id="target-element"></div>
122+
</div>
123+
);
124+
expect(findSelectorUp(container.querySelector('#target-element'), '.dummy')).toBeNull();
125+
});
126+
test('returns root element', () => {
127+
const { container } = render(
128+
<div id="root-element" className="test-class">
129+
<div id="target-element"></div>
130+
</div>
131+
);
132+
expect(findSelectorUp(container.querySelector('#target-element'), '.test-class')!.id).toBe('root-element');
133+
});
134+
test('returns parent component element with portals', () => {
135+
const { container } = render(
136+
<div>
137+
<div id="root-element" className="test-class">
138+
<div id=":rr5:"></div>
139+
</div>
140+
<div data-awsui-referrer-id=":rr5:">
141+
<div id="target-element"></div>
142+
</div>
143+
</div>
144+
);
145+
expect(findSelectorUp(container.querySelector('#target-element'), '.test-class')!.id).toBe('root-element');
146+
});
147+
test('returns null when element has no parent element with className', () => {
148+
const { container } = render(
149+
<div>
150+
<div id="target-element"></div>
151+
</div>
152+
);
153+
expect(findSelectorUp(container.querySelector('#target-element'), '.test-class')).toBeNull();
154+
});
155+
});

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,43 @@ describe('processLabel', () => {
225225
expect(processLabel(target, { selector: '.outer-class', root: 'body' })).toEqual('label outside of the component');
226226
});
227227

228+
test('respects the rootSelector property', () => {
229+
const { container } = render(
230+
<div className="root-class">
231+
<div className="label-class">outer label</div>
232+
<div id="target">
233+
<div className="label-class">inner label</div>
234+
</div>
235+
</div>
236+
);
237+
const target = container.querySelector('#target') as HTMLElement;
238+
expect(processLabel(target, { selector: '.label-class', rootSelector: '.root-class' })).toEqual('outer label');
239+
});
240+
test('rootSelector prevails over root property', () => {
241+
const { container } = render(
242+
<>
243+
<div className="root-class">
244+
<div className="label-class">root class label</div>
245+
<div {...getAnalyticsMetadataAttribute({ component: { name: 'ComponentName' } })}>
246+
<div className="label-class">component label</div>
247+
<div id="target">
248+
<div className="label-class">inner label</div>
249+
</div>
250+
</div>
251+
</div>
252+
<div className="outer-class">label outside of the component</div>
253+
</>
254+
);
255+
const target = container.querySelector('#target') as HTMLElement;
256+
expect(processLabel(target, { selector: '.label-class', root: 'self', rootSelector: '.root-class' })).toEqual(
257+
'root class label'
258+
);
259+
expect(processLabel(target, { selector: '.label-class', root: 'component', rootSelector: '.root-class' })).toEqual(
260+
'root class label'
261+
);
262+
expect(processLabel(target, { selector: '.outer-class', root: 'body', rootSelector: '.root-class' })).toEqual('');
263+
});
264+
228265
test('forwards the label resolution with data-awsui-analytics-label', () => {
229266
const { container } = render(
230267
<div>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ describe('getRawAnalyticsMetadata', () => {
5959
anotherLabel: {
6060
root: 'self',
6161
},
62+
yetAnotherLabel: {
63+
rootSelector: '.root-class-name',
64+
},
6265
},
6366
},
6467
},
@@ -74,6 +77,7 @@ describe('getRawAnalyticsMetadata', () => {
7477
'.component-label',
7578
'.component-label',
7679
'.invalid-selector',
80+
'.root-class-name',
7781
],
7882
});
7983
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ describe('getGeneratedAnalyticsMetadata', () => {
7070
position: '2',
7171
columnLabel: '',
7272
anotherLabel: 'sub labelanother text content to ignorecontentcomponent labelevent label',
73+
yetAnotherLabel: '',
7374
},
7475
},
7576
},

src/internal/analytics-metadata/dom-utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ export const isNodeComponent = (node: HTMLElement): boolean => {
3535
return false;
3636
}
3737
};
38+
39+
export function findSelectorUp(node: HTMLElement | null, selector: string): HTMLElement | null {
40+
let current: HTMLElement | null = node;
41+
while (current && current.tagName !== 'body' && !current.matches(selector)) {
42+
current = findLogicalParent(current);
43+
}
44+
return current && current.tagName !== 'body' ? current : null;
45+
}

src/internal/analytics-metadata/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ interface GeneratedAnalyticsMetadataComponentContext {
3737
export interface LabelIdentifier {
3838
selector?: string | Array<string>;
3939
root?: 'component' | 'self' | 'body';
40+
rootSelector?: string;
4041
}
4142

4243
export interface GeneratedAnalyticsMetadataFragment extends Omit<Partial<GeneratedAnalyticsMetadata>, 'detail'> {

src/internal/analytics-metadata/labels-utils.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { LABEL_DATA_ATTRIBUTE } from './attributes';
5-
import { findComponentUp } from './dom-utils';
5+
import { findSelectorUp, findComponentUp } from './dom-utils';
66
import { LabelIdentifier } from './interfaces';
77

88
export const processLabel = (node: HTMLElement | null, labelIdentifier: string | LabelIdentifier | null): string => {
@@ -13,13 +13,23 @@ export const processLabel = (node: HTMLElement | null, labelIdentifier: string |
1313
const selector = formattedLabelIdentifier.selector;
1414
if (Array.isArray(selector)) {
1515
for (const labelSelector of selector) {
16-
const label = processSingleLabel(node, labelSelector, formattedLabelIdentifier.root);
16+
const label = processSingleLabel(
17+
node,
18+
labelSelector,
19+
formattedLabelIdentifier.root,
20+
formattedLabelIdentifier.rootSelector
21+
);
1722
if (label) {
1823
return label;
1924
}
2025
}
2126
}
22-
return processSingleLabel(node, selector as string, formattedLabelIdentifier.root);
27+
return processSingleLabel(
28+
node,
29+
selector as string,
30+
formattedLabelIdentifier.root,
31+
formattedLabelIdentifier.rootSelector
32+
);
2333
};
2434

2535
const formatLabelIdentifier = (labelIdentifier: string | LabelIdentifier): LabelIdentifier => {
@@ -32,11 +42,15 @@ const formatLabelIdentifier = (labelIdentifier: string | LabelIdentifier): Label
3242
const processSingleLabel = (
3343
node: HTMLElement | null,
3444
labelSelector: string,
35-
root: LabelIdentifier['root'] = 'self'
45+
root: LabelIdentifier['root'] = 'self',
46+
rootSelector?: string
3647
): string => {
3748
if (!node) {
3849
return '';
3950
}
51+
if (rootSelector) {
52+
return processSingleLabel(findSelectorUp(node, rootSelector), labelSelector);
53+
}
4054
if (root === 'component') {
4155
return processSingleLabel(findComponentUp(node), labelSelector);
4256
}

src/internal/analytics-metadata/testing-utils.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@ const getLabelSelectors = (localMetadata: any): Array<string> => {
4444
};
4545

4646
const getLabelSelectorsFromLabelIdentifier = (label: string | LabelIdentifier): Array<string> => {
47+
let labels: Array<string> = [];
4748
if (typeof label === 'string') {
48-
return [label];
49-
} else if (label.selector) {
50-
if (typeof label.selector === 'string') {
51-
return [label.selector];
49+
labels.push(label);
50+
} else {
51+
if (label.selector) {
52+
if (typeof label.selector === 'string') {
53+
labels.push(label.selector);
54+
} else {
55+
labels = [...label.selector];
56+
}
57+
}
58+
if (label.rootSelector) {
59+
labels.push(label.rootSelector);
5260
}
53-
return label.selector;
5461
}
55-
return [];
62+
return labels;
5663
};

0 commit comments

Comments
 (0)