Skip to content

Commit bec8e60

Browse files
[WNMGDS-3166] Add eMedicare footer web-component (#3405)
* Initial commit * Update import path for define to use core design system module within monorepo. * Adds onClickLinkAnalytics handling * Adds unit tests * Adds exclude list to tsconfig.json * Adds an alias for relative path to define in webpack config. * Adds an import to react in ds-simple-footer.test.tsx * Adds react as an import to ds-accordion and ds-alert test and storybook files. * Add snapshots * Adds storybook-doc snapshot * Remove exclude list in tsconfig.json * Adds default values for argTypes * Updates storybook-docs snap * Revert out of scope changes. * Revert out of scope changes, part 2 * We no longer need to export define from index.ts * Removes setTimeout from useEffect in storybook template. * Adds website info argtype to storybook file, updates storybook-doc snapshot
1 parent b23d2f6 commit bec8e60

15 files changed

+423
-1
lines changed

packages/ds-medicare-gov/src/components/web-components/ds-simple-footer/__snapshots__/ds-simple-footer.test.tsx.snap

+168
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import type { Meta } from '@storybook/react';
2+
import { useEffect } from 'react';
3+
import { action } from '@storybook/addon-actions';
4+
import WebComponentDocTemplate from '../../../../../../.storybook/docs/WebComponentDocTemplate.mdx';
5+
import { webComponentDecorator } from '../../../../../design-system/src/components/web-components/storybook';
6+
import './ds-simple-footer';
7+
8+
const meta: Meta = {
9+
title: 'Medicare/Web Components/ds-simple-footer',
10+
decorators: [webComponentDecorator],
11+
parameters: {
12+
theme: 'medicare',
13+
docs: {
14+
page: WebComponentDocTemplate,
15+
description: {
16+
component:
17+
'For information about how and when to use this component, [refer to its full documentation page](https://design.cms.gov/components/footer/medicare-footer).',
18+
},
19+
componentEvents: {
20+
'ds-link-click-analytics': {
21+
description: 'A callback function triggered when the user clicks a link in the footer.',
22+
},
23+
},
24+
},
25+
},
26+
argTypes: {
27+
'about-medicare-label': {
28+
description: 'Label for the "About" link.',
29+
control: 'text',
30+
defaultValue: { summary: 'About' },
31+
},
32+
'nondiscrimination-label': {
33+
description: 'Label for the "Accessibility" link.',
34+
control: 'text',
35+
defaultValue: { summary: 'Accessibility' },
36+
},
37+
'privacy-policy-label': {
38+
description: 'Label for the "Privacy policy" link.',
39+
control: 'text',
40+
defaultValue: { summary: 'Privacy policy' },
41+
},
42+
'privacy-setting-label': {
43+
description: 'Label for the "Privacy setting" link.',
44+
control: 'text',
45+
defaultValue: { summary: 'Privacy setting' },
46+
},
47+
'linking-policy-label': {
48+
description: 'Label for the "Linking policy" link.',
49+
control: 'text',
50+
defaultValue: { summary: 'Linking policy' },
51+
},
52+
'using-this-site-label': {
53+
description: 'Label for the "Using this site" link.',
54+
control: 'text',
55+
defaultValue: { summary: 'Using this site' },
56+
},
57+
'plain-writing-label': {
58+
description: 'Label for the "Plain writing" link.',
59+
control: 'text',
60+
defaultValue: { summary: 'Plain writing' },
61+
},
62+
'website-info': {
63+
description:
64+
'Text describing the website’s management and funding, typically displayed in the footer. Defaults to a standard message indicating CMS ownership.',
65+
control: 'text',
66+
defaultValue: {
67+
summary:
68+
'A federal government website managed and paid for by the U.S. Centers for Medicare and Medicaid Services.',
69+
},
70+
},
71+
language: {
72+
description:
73+
"Language for the 'Privacy Setting' modal. See Tealium documentation for more information.",
74+
control: 'text',
75+
defaultValue: { summary: 'en' },
76+
},
77+
},
78+
};
79+
80+
const Template = (args) => {
81+
useEffect(() => {
82+
const footer = document.querySelector('ds-simple-footer');
83+
// Adding custom event listeners to open links in new tabs, allowing us to log and verify
84+
// the `ds-click-link-analytics` event in Storybook actions.
85+
const links = footer?.querySelectorAll('a');
86+
links?.forEach((link) => {
87+
link.setAttribute('target', '_blank');
88+
link.addEventListener('click', (e) => {
89+
e.preventDefault();
90+
window.open(link.href, '_blank');
91+
});
92+
});
93+
94+
const handleAnalyticsEvent = (event: Event) => {
95+
const customEvent = event as CustomEvent;
96+
event.preventDefault();
97+
action('ds-click-link-analytics')(customEvent);
98+
};
99+
100+
footer.addEventListener('ds-click-link-analytics', handleAnalyticsEvent);
101+
102+
return () => {
103+
links?.forEach((link) => {
104+
link.removeEventListener('click', (e) => e.preventDefault());
105+
});
106+
footer.removeEventListener('ds-click-link-analytics', handleAnalyticsEvent);
107+
};
108+
}, []);
109+
return <ds-simple-footer {...args}></ds-simple-footer>;
110+
};
111+
112+
export const Default = Template.bind({});
113+
114+
export default meta;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { render, screen } from '@testing-library/preact';
2+
import userEvent from '@testing-library/user-event';
3+
import './ds-simple-footer';
4+
5+
function renderFooter(props = {}) {
6+
return render(<ds-simple-footer {...props} />);
7+
}
8+
9+
describe('SimpleFooter', () => {
10+
beforeEach(() => {
11+
jest.clearAllMocks();
12+
document.body.innerHTML = '';
13+
});
14+
15+
it('renders without crashing', async () => {
16+
const { asFragment } = renderFooter({ 'about-medicare-label': 'About Medicare' });
17+
expect(asFragment()).toMatchSnapshot();
18+
expect(screen.getByText(/About Medicare/i)).toBeInTheDocument();
19+
});
20+
21+
it('calls ds-click-link-analytics when a link is clicked', async () => {
22+
const mockAnalyticsCallback = jest.fn();
23+
24+
renderFooter({ 'about-medicare-label': 'About Medicare' });
25+
26+
const footer = document.querySelector('ds-simple-footer');
27+
expect(footer).toBeInTheDocument();
28+
29+
footer?.addEventListener('ds-click-link-analytics', mockAnalyticsCallback);
30+
31+
const link = screen.getByText(/About Medicare/i);
32+
await userEvent.click(link);
33+
34+
expect(mockAnalyticsCallback).toHaveBeenCalledTimes(1);
35+
36+
const event = mockAnalyticsCallback.mock.calls[0][0];
37+
expect(event.detail.url).toBe('https://www.medicare.gov/about-us');
38+
39+
footer?.removeEventListener('ds-click-link-analytics', mockAnalyticsCallback);
40+
});
41+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { SimpleFooter } from '../../SimpleFooter';
2+
import { define } from '../../../../../design-system/src/components/web-components/preactement/define';
3+
4+
const attributes = [
5+
'about-medicare-label',
6+
'nondiscrimination-label',
7+
'privacy-policy-label',
8+
'privacy-setting-label',
9+
'linking-policy-label',
10+
'using-this-site-label',
11+
'plain-writing-label',
12+
'website-info',
13+
'language',
14+
];
15+
16+
const Wrapper = ({ ...otherProps }) => {
17+
return <SimpleFooter {...otherProps}></SimpleFooter>;
18+
};
19+
20+
/* eslint-disable @typescript-eslint/no-namespace */
21+
declare global {
22+
namespace JSX {
23+
interface IntrinsicElements {
24+
'ds-simple-footer': JSX.IntrinsicElements['div'] & {
25+
[K in (typeof attributes)[number]]?: string;
26+
};
27+
}
28+
}
29+
}
30+
/* eslint-enable */
31+
32+
define('ds-simple-footer', () => Wrapper, {
33+
attributes,
34+
events: [
35+
[
36+
'onClickLinkAnalytics',
37+
(url: string) => ({
38+
detail: { url },
39+
}),
40+
],
41+
],
42+
} as any);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './ds-simple-footer';

packages/ds-medicare-gov/src/components/web-components/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@
1010
* modules during tree-shaking.
1111
*/
1212

13+
import './ds-simple-footer';
14+
1315
export * from '@cmsgov/design-system/web-components';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
[
3+
"about-medicare-label",
4+
"Label for the \"About\" link.",
5+
"string"
6+
],
7+
[
8+
"language",
9+
"Language for the 'Privacy Setting' modal. See Tealium documentation for more information.",
10+
"string"
11+
],
12+
[
13+
"linking-policy-label",
14+
"Label for the \"Linking policy\" link.",
15+
"string"
16+
],
17+
[
18+
"nondiscrimination-label",
19+
"Label for the \"Accessibility\" link.",
20+
"string"
21+
],
22+
[
23+
"plain-writing-label",
24+
"Label for the \"Plain writing\" link.",
25+
"string"
26+
],
27+
[
28+
"privacy-policy-label",
29+
"Label for the \"Privacy policy\" link.",
30+
"string"
31+
],
32+
[
33+
"privacy-setting-label",
34+
"Label for the \"Privacy setting\" link.",
35+
"string"
36+
],
37+
[
38+
"using-this-site-label",
39+
"Label for the \"Using this site\" link.",
40+
"string"
41+
],
42+
[
43+
"website-info",
44+
"Text describing the website’s management and funding, typically displayed in the footer. Defaults to a standard message indicating CMS ownership.",
45+
"string"
46+
]
47+
]

tests/unit/jest.config.js

+2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ const conditionalWebComponentsConfig = useWebComponents
1717
? {
1818
testMatch: [
1919
'<rootDir>/packages/design-system/src/components/web-components/**/*.test.[jt]s(x)?',
20+
'<rootDir>/packages/ds-medicare-gov/src/components/web-components/**/*.test.[jt]s(x)?',
2021
'<rootDir>/tests/browser/custom-reporter.test.ts',
2122
],
2223
}
2324
: {
2425
testPathIgnorePatterns: [
2526
'<rootDir>/packages/design-system/src/components/web-components',
27+
'<rootDir>/packages/ds-medicare-gov/src/components/web-components',
2628
'<rootDir>/tests/browser/',
2729
],
2830
};

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"rootDir": ".",
34
"target": "es2015",
45
"jsx": "react-jsx",
56
"module": "esnext",

webpack.config.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ function generateWebpackConfig(options) {
9797
})
9898
);
9999
}
100-
101100
if (options.preact) {
102101
// If we're using preact, we need to replace resolve references to react
103102
// to their preact equivalents.
@@ -106,6 +105,11 @@ function generateWebpackConfig(options) {
106105
react: 'preact/compat',
107106
'react-dom': 'preact/compat',
108107
'react/jsx-runtime': 'preact/jsx-runtime',
108+
'../../../../../design-system/src/components/web-components/preactement/define':
109+
path.resolve(
110+
__dirname,
111+
'packages/design-system/dist/preact-components/esm/web-components/preactement/define.js'
112+
),
109113
},
110114
};
111115

0 commit comments

Comments
 (0)