Skip to content
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
7 changes: 4 additions & 3 deletions src/internal/analytics-metadata/__tests__/components.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import React, { ReactNode } from 'react';
import { METADATA_ATTRIBUTE, getAnalyticsMetadataAttribute, getAnalyticsLabelAttribute } from '../attributes';

export const ComponentOne = ({ malformed }: { malformed?: boolean }) => (
Expand Down Expand Up @@ -33,7 +33,7 @@ export const ComponentOne = ({ malformed }: { malformed?: boolean }) => (
</div>
);

const ComponentTwo = () => (
export const ComponentTwo = () => (
<div {...getAnalyticsMetadataAttribute({ component: { name: 'ComponentTwo', label: '.component-label' } })}>
<div className="component-label" {...getAnalyticsLabelAttribute('.sub-label')}>
<div className="sub-label">sub label</div>
Expand All @@ -43,7 +43,7 @@ const ComponentTwo = () => (
</div>
);

export const ComponentThree = () => (
export const ComponentThree = ({ children }: { children?: ReactNode }) => (
<div {...getAnalyticsMetadataAttribute({ component: { name: 'ComponentThree' } })}>
<div
{...getAnalyticsMetadataAttribute({
Expand All @@ -61,6 +61,7 @@ export const ComponentThree = () => (
<div data-awsui-referrer-id="id:nested:portal">
<ComponentOne />
</div>
{children}
</div>
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { render } from '@testing-library/react';
import { getComponentsTree } from '../utils';
import { METADATA_ATTRIBUTE, activateAnalyticsMetadata } from '../attributes';
import { ComponentOne, ComponentTwo, ComponentThree } from './components';

describe('getComponentsTree', () => {
describe('with active analytics metadata', () => {
beforeAll(() => {
activateAnalyticsMetadata(true);
});
test('returns an empty array when input is null', () => {
expect(getComponentsTree(null)).toEqual([]);
});
test('skips metadata that does not refer to a component', () => {
const { container } = render(
<div id="outer-target">
<ComponentOne />
</div>
);
const target = container.querySelector('#outer-target') as HTMLElement;
expect(getComponentsTree(target)).toEqual([
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
]);
});
test('only includes components inside the specified element', () => {
const { container } = render(
<>
<div id="outer-target-1">
<ComponentOne />
</div>
<div id="outer-target-2">
<ComponentTwo />
</div>
</>
);
expect(getComponentsTree(container.querySelector('#outer-target-1') as HTMLElement)).toEqual([
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
]);
expect(getComponentsTree(container.querySelector('#outer-target-2') as HTMLElement)).toEqual([
{ name: 'ComponentTwo', label: 'sub label', children: [] },
]);
});
test('can include multiple components', () => {
const { container } = render(
<div id="outer-target-1">
<ComponentThree />
<ComponentTwo />
</div>
);
expect(getComponentsTree(container.querySelector('#outer-target-1') as HTMLElement)).toEqual([
{
name: 'ComponentThree',
children: [
{ name: 'ComponentTwo', label: 'sub label', children: [] },
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
],
},
{ name: 'ComponentTwo', label: 'sub label', children: [] },
]);
});
test('can include multiple nested components', () => {
const { container } = render(
<div id="outer-target-1">
<ComponentThree>
<ComponentThree />
</ComponentThree>
</div>
);
expect(getComponentsTree(container.querySelector('#outer-target-1') as HTMLElement)).toEqual([
{
name: 'ComponentThree',
children: [
{ name: 'ComponentTwo', label: 'sub label', children: [] },
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
{
name: 'ComponentThree',
children: [
{ name: 'ComponentTwo', label: 'sub label', children: [] },
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
],
},
],
},
]);
});
test('use document as default element', () => {
render(
<>
<ComponentThree />
<ComponentTwo />
</>
);
expect(getComponentsTree()).toEqual([
{
name: 'ComponentThree',
children: [
{ name: 'ComponentTwo', label: 'sub label', children: [] },
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
],
},
{ name: 'ComponentTwo', label: 'sub label', children: [] },
]);
});
test('skips malformed metadata', () => {
const { container } = render(
<div id="outer-target" {...{ [METADATA_ATTRIBUTE]: "{'corruptedJSON':}" }}>
<ComponentOne malformed={true} />
</div>
);
const target = container.querySelector('#outer-target') as HTMLElement;
expect(getComponentsTree(target)).toEqual([
{ name: 'ComponentOne', label: 'component label', properties: { multi: 'true' }, children: [] },
]);
});
});

describe('with inactive analytics metadata', () => {
beforeAll(() => {
activateAnalyticsMetadata(false);
});
test('returns an empty object', () => {
const { container } = render(<ComponentThree />);
const target = container.querySelector('#target') as HTMLElement;
expect(getComponentsTree(target)).toEqual([]);
});
});
});
2 changes: 1 addition & 1 deletion src/internal/analytics-metadata/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface GeneratedAnalyticsMetadata {
contexts: Array<GeneratedAnalyticsMetadataComponentContext>;
}

interface GeneratedAnalyticsMetadataComponent {
export interface GeneratedAnalyticsMetadataComponent {
// name of the component. For example: "awsui.RadioGroup". We prefix the actual name with awsui to account for future tagging of custom components
name: string;

Expand Down
44 changes: 44 additions & 0 deletions src/internal/analytics-metadata/page-scanner-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { METADATA_ATTRIBUTE } from './attributes';
import { isNodeComponent } from './dom-utils';
import { GeneratedAnalyticsMetadataComponent } from './interfaces';
import { getGeneratedAnalyticsMetadata } from './utils';

interface GeneratedAnalyticsMetadataComponentTree extends GeneratedAnalyticsMetadataComponent {
children?: Array<GeneratedAnalyticsMetadataComponentTree>;
}

const getComponentsArray = (node: HTMLElement | Document = document) => {
const elementsWithMetadata = Array.from(node.querySelectorAll(`[${METADATA_ATTRIBUTE}]`)) as Array<HTMLElement>;
return elementsWithMetadata.filter(isNodeComponent);
};

const getComponentsTreeRecursive = (
node: HTMLElement | Document,
visited: Set<HTMLElement>
): Array<GeneratedAnalyticsMetadataComponentTree> => {
const tree: Array<GeneratedAnalyticsMetadataComponentTree> = [];
const componentNodes = getComponentsArray(node);
componentNodes.forEach(componentNode => {
if (visited.has(componentNode)) {
return;
}
visited.add(componentNode);
tree.push({
...getGeneratedAnalyticsMetadata(componentNode).contexts[0].detail,
children: getComponentsTreeRecursive(componentNode, visited),
});
});
return tree;
};

export const getComponentsTree = (
node: HTMLElement | Document | null = document
): Array<GeneratedAnalyticsMetadataComponentTree> => {
if (!node) {
return [];
}
return getComponentsTreeRecursive(node, new Set());
};
1 change: 1 addition & 0 deletions src/internal/analytics-metadata/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

export { getRawAnalyticsMetadata } from './testing-utils';
export { getComponentsTree } from './page-scanner-utils';

import { METADATA_DATA_ATTRIBUTE } from './attributes';
import { GeneratedAnalyticsMetadata, GeneratedAnalyticsMetadataFragment } from './interfaces';
Expand Down
Loading