Skip to content

Commit 5c902d3

Browse files
committed
ENH Refactor campaing-admin from class to functional component
1 parent bac3091 commit 5c902d3

5 files changed

Lines changed: 537 additions & 133 deletions

File tree

client/dist/js/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 38 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,28 @@
1-
import React, { Component } from 'react';
1+
import React, { useEffect, useRef } from 'react';
22
import PropTypes from 'prop-types';
33
import i18n from 'i18n';
44
import CONSTANTS from 'constants/index';
55

66
const noop = () => null;
77

8-
class IntroScreen extends Component {
9-
constructor(props) {
10-
super(props);
8+
const IntroScreen = ({
9+
show = false,
10+
onClose = noop,
11+
focusCloseButton = false,
12+
}) => {
13+
const closeButtonRef = useRef(null);
1114

12-
this.handleClose = this.handleClose.bind(this);
13-
this.closeButtonRef = React.createRef();
14-
}
15-
16-
componentDidMount() {
17-
if (this.props.focusCloseButton && this.closeButtonRef.current) {
18-
this.closeButtonRef.current.focus();
19-
}
20-
}
21-
22-
componentDidUpdate(prevProps) {
23-
if (
24-
this.props.focusCloseButton &&
25-
!prevProps.focusCloseButton &&
26-
this.closeButtonRef.current
27-
) {
28-
this.closeButtonRef.current.focus();
15+
useEffect(() => {
16+
if (focusCloseButton && closeButtonRef.current) {
17+
closeButtonRef.current.focus();
2918
}
30-
}
19+
}, [focusCloseButton]);
3120

32-
handleClose(e) {
33-
this.props.onClose(e);
34-
}
21+
const handleClose = (e) => {
22+
onClose(e);
23+
};
3524

36-
renderContent() {
25+
const renderContent = () => {
3726
const button = CONSTANTS.infoScreen.callToAction;
3827
const links = CONSTANTS.infoScreen.links;
3928
return (
@@ -59,44 +48,36 @@ class IntroScreen extends Component {
5948
</div>
6049
</div>
6150
);
62-
}
51+
};
6352

64-
render() {
65-
if (!this.props.show) {
66-
return null;
67-
}
68-
return (
69-
<div className="fill-width campaign-info" id="campaign-info">
70-
<div className="campaign-info__buttons">
71-
<button
72-
className="btn campaign-info__close btn--no-text font-icon-cancel btn--icon-xl"
73-
onClick={this.handleClose}
74-
aria-label={i18n._t('CampaignAdmin.HELP_HIDE', 'Hide help')}
75-
aria-expanded="true"
76-
aria-controls="campaign-info"
77-
ref={this.closeButtonRef}
78-
/>
79-
</div>
80-
<div className="campaign-info__banner-image" />
81-
<div className="campaign-info__icon">
82-
<span className="font-icon-white-question icon btn--icon-xl btn--no-text" aria-hidden="true" />
83-
</div>
84-
{this.renderContent()}
85-
</div>
86-
);
53+
if (!show) {
54+
return null;
8755
}
88-
}
56+
return (
57+
<div className="fill-width campaign-info" id="campaign-info">
58+
<div className="campaign-info__buttons">
59+
<button
60+
className="btn campaign-info__close btn--no-text font-icon-cancel btn--icon-xl"
61+
onClick={handleClose}
62+
aria-label={i18n._t('CampaignAdmin.HELP_HIDE', 'Hide help')}
63+
aria-expanded="true"
64+
aria-controls="campaign-info"
65+
ref={closeButtonRef}
66+
/>
67+
</div>
68+
<div className="campaign-info__banner-image" />
69+
<div className="campaign-info__icon">
70+
<span className="font-icon-white-question icon btn--icon-xl btn--no-text" aria-hidden="true" />
71+
</div>
72+
{renderContent()}
73+
</div>
74+
);
75+
};
8976

9077
IntroScreen.propTypes = {
9178
show: PropTypes.bool,
9279
onClose: PropTypes.func,
9380
focusCloseButton: PropTypes.bool,
9481
};
9582

96-
IntroScreen.defaultProps = {
97-
show: false,
98-
onClose: noop,
99-
focusCloseButton: false,
100-
};
101-
10283
export default IntroScreen;

client/src/components/IntroScreen/tests/IntroScreen-test.js

Lines changed: 188 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
/* global jest, test, describe, it, expect */
1+
/* global jest, test, describe, it, expect, beforeEach, afterEach */
22

33
import React from 'react';
4-
import { render } from '@testing-library/react';
4+
import { render, screen, fireEvent } from '@testing-library/react';
55
import IntroScreen from '../IntroScreen';
66

77
test('IntroScreen renders when show is true', () => {
@@ -25,3 +25,189 @@ test('IntroScreen does not render when show is false', () => {
2525
);
2626
expect(container.querySelector('.campaign-info')).toBeNull();
2727
});
28+
29+
test('IntroScreen close button calls onClose handler', () => {
30+
const onClose = jest.fn();
31+
const { container } = render(
32+
<IntroScreen
33+
show
34+
onClose={onClose}
35+
/>
36+
);
37+
const closeButton = container.querySelector('.campaign-info__close');
38+
fireEvent.click(closeButton);
39+
expect(onClose).toHaveBeenCalledTimes(1);
40+
});
41+
42+
test('IntroScreen close button passes event to onClose handler', () => {
43+
const onClose = jest.fn();
44+
const { container } = render(
45+
<IntroScreen
46+
show
47+
onClose={onClose}
48+
/>
49+
);
50+
const closeButton = container.querySelector('.campaign-info__close');
51+
fireEvent.click(closeButton);
52+
expect(onClose.mock.calls[0][0]).toBeDefined();
53+
expect(onClose.mock.calls[0][0].type).toBe('click');
54+
});
55+
56+
test('IntroScreen renders info section header', () => {
57+
render(
58+
<IntroScreen
59+
show
60+
onClose={() => {}}
61+
/>
62+
);
63+
expect(screen.getByText('How do campaigns work?')).not.toBeNull();
64+
});
65+
66+
test('IntroScreen renders info section content text', () => {
67+
render(
68+
<IntroScreen
69+
show
70+
onClose={() => {}}
71+
/>
72+
);
73+
expect(screen.getByText(/Campaigns allow multiple users to publish large amounts of content/)).not.toBeNull();
74+
});
75+
76+
test('IntroScreen renders banner image element', () => {
77+
const { container } = render(
78+
<IntroScreen
79+
show
80+
onClose={() => {}}
81+
/>
82+
);
83+
expect(container.querySelector('.campaign-info__banner-image')).not.toBeNull();
84+
});
85+
86+
test('IntroScreen renders help icon', () => {
87+
const { container } = render(
88+
<IntroScreen
89+
show
90+
onClose={() => {}}
91+
/>
92+
);
93+
const icon = container.querySelector('.font-icon-white-question');
94+
expect(icon).not.toBeNull();
95+
expect(icon.getAttribute('aria-hidden')).toBe('true');
96+
});
97+
98+
test('IntroScreen close button has correct accessibility attributes', () => {
99+
const { container } = render(
100+
<IntroScreen
101+
show
102+
onClose={() => {}}
103+
/>
104+
);
105+
const closeButton = container.querySelector('.campaign-info__close');
106+
expect(closeButton.getAttribute('aria-label')).toBe('Hide help');
107+
expect(closeButton.getAttribute('aria-expanded')).toBe('true');
108+
expect(closeButton.getAttribute('aria-controls')).toBe('campaign-info');
109+
});
110+
111+
test('IntroScreen close button has correct CSS classes', () => {
112+
const { container } = render(
113+
<IntroScreen
114+
show
115+
onClose={() => {}}
116+
/>
117+
);
118+
const closeButton = container.querySelector('.campaign-info__close');
119+
expect(closeButton.classList.contains('btn')).toBe(true);
120+
expect(closeButton.classList.contains('campaign-info__close')).toBe(true);
121+
expect(closeButton.classList.contains('btn--no-text')).toBe(true);
122+
expect(closeButton.classList.contains('font-icon-cancel')).toBe(true);
123+
expect(closeButton.classList.contains('btn--icon-xl')).toBe(true);
124+
});
125+
126+
test('IntroScreen renders content container with correct class', () => {
127+
const { container } = render(
128+
<IntroScreen
129+
show
130+
onClose={() => {}}
131+
/>
132+
);
133+
expect(container.querySelector('.campaign-info__content')).not.toBeNull();
134+
});
135+
136+
test('IntroScreen renders flexbox-area-grow class on content', () => {
137+
const { container } = render(
138+
<IntroScreen
139+
show
140+
onClose={() => {}}
141+
/>
142+
);
143+
const content = container.querySelector('.campaign-info__content');
144+
expect(content.classList.contains('flexbox-area-grow')).toBe(true);
145+
});
146+
147+
test('IntroScreen renders links container', () => {
148+
const { container } = render(
149+
<IntroScreen
150+
show
151+
onClose={() => {}}
152+
/>
153+
);
154+
expect(container.querySelector('.campaign-info__links')).not.toBeNull();
155+
});
156+
157+
test('IntroScreen renders content buttons container', () => {
158+
const { container } = render(
159+
<IntroScreen
160+
show
161+
onClose={() => {}}
162+
/>
163+
);
164+
expect(container.querySelector('.campaign-info__content-buttons')).not.toBeNull();
165+
});
166+
167+
test('IntroScreen main container has correct id', () => {
168+
const { container } = render(
169+
<IntroScreen
170+
show
171+
onClose={() => {}}
172+
/>
173+
);
174+
expect(container.querySelector('#campaign-info')).not.toBeNull();
175+
});
176+
177+
test('IntroScreen renders with default props when not provided', () => {
178+
const { container } = render(<IntroScreen />);
179+
expect(container.querySelector('.campaign-info')).toBeNull();
180+
});
181+
182+
test('IntroScreen has default onClose handler that does nothing', () => {
183+
const { container } = render(
184+
<IntroScreen show />
185+
);
186+
const closeButton = container.querySelector('.campaign-info__close');
187+
expect(() => {
188+
fireEvent.click(closeButton);
189+
}).not.toThrow();
190+
});
191+
192+
test('IntroScreen close button is not focused by default', () => {
193+
const { container } = render(
194+
<IntroScreen
195+
show
196+
onClose={() => {}}
197+
focusCloseButton={false}
198+
/>
199+
);
200+
const closeButton = container.querySelector('.campaign-info__close');
201+
expect(document.activeElement).not.toBe(closeButton);
202+
});
203+
204+
test('IntroScreen renders header with h3 tag', () => {
205+
const { container } = render(
206+
<IntroScreen
207+
show
208+
onClose={() => {}}
209+
/>
210+
);
211+
const heading = container.querySelector('.campaign-info__content h3');
212+
expect(heading).not.toBeNull();
213+
});

0 commit comments

Comments
 (0)