Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow to specify a root class name as a label identifier in ana… #97

Merged
merged 4 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/internal/analytics-metadata/__tests__/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const ComponentThree = () => (
position: '2',
columnLabel: { selector: '.invalid-selector', root: 'self' },
anotherLabel: { root: 'self' },
yetAnotherLabel: { rootSelector: '.root-class-name' },
},
},
})}
Expand Down
49 changes: 45 additions & 4 deletions src/internal/analytics-metadata/__tests__/dom-utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { activateAnalyticsMetadata, getAnalyticsMetadataAttribute, METADATA_ATTRIBUTE } from '../attributes';
import { findLogicalParent, isNodeComponent, findComponentUp } from '../dom-utils';
import { findLogicalParent, isNodeComponent, findComponentUp, findSelectorUp } from '../dom-utils';

beforeAll(() => {
activateAnalyticsMetadata(true);
Expand All @@ -18,7 +18,7 @@ describe('findLogicalParent', () => {
</div>
);
const child = container.querySelector('#child');
expect(findLogicalParent(child as HTMLElement)?.id).toEqual('parent');
expect(findLogicalParent(child as HTMLElement)!.id).toEqual('parent');
});
test('returns null when child does not exist', () => {
const { container } = render(
Expand Down Expand Up @@ -88,7 +88,7 @@ describe('findComponentUp', () => {
<div id="target-element"></div>
</div>
);
expect(findComponentUp(container.querySelector('#target-element'))?.id).toBe('component-element');
expect(findComponentUp(container.querySelector('#target-element'))!.id).toBe('component-element');
});
test('returns parent component element with portals', () => {
const { container } = render(
Expand All @@ -101,7 +101,7 @@ describe('findComponentUp', () => {
</div>
</div>
);
expect(findComponentUp(container.querySelector('#target-element'))?.id).toBe('component-element');
expect(findComponentUp(container.querySelector('#target-element'))!.id).toBe('component-element');
});
test('returns null when element has no parent component', () => {
const { container } = render(
Expand All @@ -112,3 +112,44 @@ describe('findComponentUp', () => {
expect(findComponentUp(container.querySelector('#target-element'))).toBeNull();
});
});

describe('findSelectorUp', () => {
test('returns null when the node is null or the className is invalid', () => {
expect(findSelectorUp(null, 'abcd')).toBeNull();
const { container } = render(
<div id="root-element">
<div id="target-element"></div>
</div>
);
expect(findSelectorUp(container.querySelector('#target-element'), '.dummy')).toBeNull();
});
test('returns root element', () => {
const { container } = render(
<div id="root-element" className="test-class">
<div id="target-element"></div>
</div>
);
expect(findSelectorUp(container.querySelector('#target-element'), '.test-class')!.id).toBe('root-element');
});
test('returns parent component element with portals', () => {
const { container } = render(
<div>
<div id="root-element" className="test-class">
<div id=":rr5:"></div>
</div>
<div data-awsui-referrer-id=":rr5:">
<div id="target-element"></div>
</div>
</div>
);
expect(findSelectorUp(container.querySelector('#target-element'), '.test-class')!.id).toBe('root-element');
});
test('returns null when element has no parent element with className', () => {
const { container } = render(
<div>
<div id="target-element"></div>
</div>
);
expect(findSelectorUp(container.querySelector('#target-element'), '.test-class')).toBeNull();
});
});
37 changes: 37 additions & 0 deletions src/internal/analytics-metadata/__tests__/labels-utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,43 @@ describe('processLabel', () => {
expect(processLabel(target, { selector: '.outer-class', root: 'body' })).toEqual('label outside of the component');
});

test('respects the rootSelector property', () => {
const { container } = render(
<div className="root-class">
<div className="label-class">outer label</div>
<div id="target">
<div className="label-class">inner label</div>
</div>
</div>
);
const target = container.querySelector('#target') as HTMLElement;
expect(processLabel(target, { selector: '.label-class', rootSelector: '.root-class' })).toEqual('outer label');
});
test('rootSelector prevails over root property', () => {
const { container } = render(
<>
<div className="root-class">
<div className="label-class">root class label</div>
<div {...getAnalyticsMetadataAttribute({ component: { name: 'ComponentName' } })}>
<div className="label-class">component label</div>
<div id="target">
<div className="label-class">inner label</div>
</div>
</div>
</div>
<div className="outer-class">label outside of the component</div>
</>
);
const target = container.querySelector('#target') as HTMLElement;
expect(processLabel(target, { selector: '.label-class', root: 'self', rootSelector: '.root-class' })).toEqual(
'root class label'
pan-kot marked this conversation as resolved.
Show resolved Hide resolved
);
expect(processLabel(target, { selector: '.label-class', root: 'component', rootSelector: '.root-class' })).toEqual(
'root class label'
);
expect(processLabel(target, { selector: '.outer-class', root: 'body', rootSelector: '.root-class' })).toEqual('');
});

test('forwards the label resolution with data-awsui-analytics-label', () => {
const { container } = render(
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ describe('getRawAnalyticsMetadata', () => {
anotherLabel: {
root: 'self',
},
yetAnotherLabel: {
rootSelector: '.root-class-name',
},
},
},
},
Expand All @@ -74,6 +77,7 @@ describe('getRawAnalyticsMetadata', () => {
'.component-label',
'.component-label',
'.invalid-selector',
'.root-class-name',
],
});
});
Expand Down
1 change: 1 addition & 0 deletions src/internal/analytics-metadata/__tests__/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe('getGeneratedAnalyticsMetadata', () => {
position: '2',
columnLabel: '',
anotherLabel: 'sub labelanother text content to ignorecontentcomponent labelevent label',
yetAnotherLabel: '',
},
},
},
Expand Down
8 changes: 8 additions & 0 deletions src/internal/analytics-metadata/dom-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ export const isNodeComponent = (node: HTMLElement): boolean => {
return false;
}
};

export function findSelectorUp(node: HTMLElement | null, selector: string): HTMLElement | null {
let current: HTMLElement | null = node;
while (current && current.tagName !== 'body' && !current.matches(selector)) {
current = findLogicalParent(current);
}
return current && current.tagName !== 'body' ? current : null;
}
1 change: 1 addition & 0 deletions src/internal/analytics-metadata/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface GeneratedAnalyticsMetadataComponentContext {
export interface LabelIdentifier {
selector?: string | Array<string>;
root?: 'component' | 'self' | 'body';
rootSelector?: string;
}

export interface GeneratedAnalyticsMetadataFragment extends Omit<Partial<GeneratedAnalyticsMetadata>, 'detail'> {
Expand Down
22 changes: 18 additions & 4 deletions src/internal/analytics-metadata/labels-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { LABEL_DATA_ATTRIBUTE } from './attributes';
import { findComponentUp } from './dom-utils';
import { findSelectorUp, findComponentUp } from './dom-utils';
import { LabelIdentifier } from './interfaces';

export const processLabel = (node: HTMLElement | null, labelIdentifier: string | LabelIdentifier | null): string => {
Expand All @@ -13,13 +13,23 @@ export const processLabel = (node: HTMLElement | null, labelIdentifier: string |
const selector = formattedLabelIdentifier.selector;
if (Array.isArray(selector)) {
for (const labelSelector of selector) {
const label = processSingleLabel(node, labelSelector, formattedLabelIdentifier.root);
const label = processSingleLabel(
node,
labelSelector,
formattedLabelIdentifier.root,
formattedLabelIdentifier.rootSelector
);
if (label) {
return label;
}
}
}
return processSingleLabel(node, selector as string, formattedLabelIdentifier.root);
return processSingleLabel(
node,
selector as string,
formattedLabelIdentifier.root,
formattedLabelIdentifier.rootSelector
);
};

const formatLabelIdentifier = (labelIdentifier: string | LabelIdentifier): LabelIdentifier => {
Expand All @@ -32,11 +42,15 @@ const formatLabelIdentifier = (labelIdentifier: string | LabelIdentifier): Label
const processSingleLabel = (
node: HTMLElement | null,
labelSelector: string,
root: LabelIdentifier['root'] = 'self'
root: LabelIdentifier['root'] = 'self',
rootSelector?: string
): string => {
if (!node) {
return '';
}
if (rootSelector) {
return processSingleLabel(findSelectorUp(node, rootSelector), labelSelector);
}
if (root === 'component') {
return processSingleLabel(findComponentUp(node), labelSelector);
}
Expand Down
19 changes: 13 additions & 6 deletions src/internal/analytics-metadata/testing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,20 @@ const getLabelSelectors = (localMetadata: any): Array<string> => {
};

const getLabelSelectorsFromLabelIdentifier = (label: string | LabelIdentifier): Array<string> => {
let labels: Array<string> = [];
if (typeof label === 'string') {
return [label];
} else if (label.selector) {
if (typeof label.selector === 'string') {
return [label.selector];
labels.push(label);
} else {
if (label.selector) {
if (typeof label.selector === 'string') {
labels.push(label.selector);
} else {
labels = [...label.selector];
}
}
if (label.rootSelector) {
labels.push(label.rootSelector);
}
return label.selector;
}
return [];
return labels;
};
Loading