}
export class GlobalNavUser extends React.PureComponent<Props> {
+ focusNode = (node: HTMLAnchorElement | null) => {
+ if (node) {
+ node.focus();
+ }
+ };
+
handleLogin = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
const returnTo = encodeURIComponent(window.location.pathname + window.location.search);
</li>
<li className="divider" />
<li>
- <Link to="/account">{translate('my_account.page')}</Link>
+ <Link ref={this.focusNode} to="/account">
+ {translate('my_account.page')}
+ </Link>
</li>
<li>
<a href="#" onClick={this.handleLogout}>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { mockCurrentUser, mockLoggedInUser, mockRouter } from '../../../../../helpers/testMocks';
+import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
import { GlobalNavUser } from '../GlobalNavUser';
it('should render the right interface for anonymous user', () => {
- expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot();
+ renderGlobalNavUser({ currentUser: mockCurrentUser() });
+ expect(screen.getByText('layout.login')).toBeInTheDocument();
});
-it('should render the right interface for logged in user', () => {
- const wrapper = shallowRender();
- wrapper.setState({ open: true });
- expect(wrapper.find('Dropdown')).toMatchSnapshot();
+it('should render the right interface for logged in user', async () => {
+ const user = userEvent.setup();
+ renderGlobalNavUser();
+ await user.click(screen.getByRole('link'));
+
+ expect(screen.getByRole('link', { name: 'my_account.page' })).toHaveFocus();
});
-function shallowRender(overrides: Partial<GlobalNavUser['props']> = {}) {
- return shallow<GlobalNavUser>(
+function renderGlobalNavUser(overrides: Partial<GlobalNavUser['props']> = {}) {
+ return renderComponent(
<GlobalNavUser currentUser={mockLoggedInUser()} router={mockRouter()} {...overrides} />
);
}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render the right interface for anonymous user 1`] = `
-<div>
- <a
- className="navbar-login"
- href="/sessions/new"
- onClick={[Function]}
- >
- layout.login
- </a>
-</div>
-`;
-
-exports[`should render the right interface for logged in user 1`] = `
-<Dropdown
- className="js-user-authenticated"
- overlay={
- <ul
- className="menu"
- >
- <li
- className="menu-item"
- >
- <div
- className="text-ellipsis text-muted"
- title="Skywalker"
- >
- <strong>
- Skywalker
- </strong>
- </div>
- </li>
- <li
- className="divider"
- />
- <li>
- <Link
- to="/account"
- >
- my_account.page
- </Link>
- </li>
- <li>
- <a
- href="#"
- onClick={[Function]}
- >
- layout.logout
- </a>
- </li>
- </ul>
- }
->
- <a
- className="dropdown-toggle navbar-avatar"
- href="#"
- title="Skywalker"
- >
- <withAppStateContext(Avatar)
- name="Skywalker"
- size={32}
- />
- </a>
-</Dropdown>
-`;
}
export default class EmbedDocsPopup extends React.PureComponent<Props> {
+ firstItem: HTMLAnchorElement | null = null;
+
+ /*
+ * Will be called by the first suggestion (if any), as well as the first link (documentation)
+ * Since we don't know if we have any suggestions, we need to allow both to make the call.
+ * If we have at least 1 suggestion, it will make the call first, and prevent 'documentation' from
+ * getting the focus.
+ */
+ focusFirstItem = (node: HTMLAnchorElement | null) => {
+ if (node && !this.firstItem) {
+ this.firstItem = node;
+ this.firstItem.focus();
+ }
+ };
+
renderTitle(text: string) {
return (
<li role="presentation" className="menu-header">
return (
<ul className="menu abs-width-240" role="group">
{this.renderTitle(translate('embed_docs.suggestion'))}
- {suggestions.map(suggestion => (
+ {suggestions.map((suggestion, i) => (
<li key={suggestion.link}>
- <Link onClick={this.props.onClose} target="_blank" to={suggestion.link}>
+ <Link
+ ref={i === 0 ? this.focusFirstItem : undefined}
+ onClick={this.props.onClose}
+ target="_blank"
+ to={suggestion.link}>
{suggestion.text}
</Link>
</li>
<SuggestionsContext.Consumer>{this.renderSuggestions}</SuggestionsContext.Consumer>
<ul className="menu abs-width-240" role="group">
<li>
- <Link onClick={this.props.onClose} target="_blank" to="/documentation">
+ <Link
+ ref={this.focusFirstItem}
+ onClick={this.props.onClose}
+ target="_blank"
+ to="/documentation">
{translate('embed_docs.documentation')}
</Link>
</li>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { shallow } from 'enzyme';
+import { screen } from '@testing-library/react';
import * as React from 'react';
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import { SuggestionLink } from '../../../types/types';
import EmbedDocsPopup from '../EmbedDocsPopup';
+import { SuggestionsContext } from '../SuggestionsContext';
-it('should render', () => {
- const wrapper = shallow(<EmbedDocsPopup onClose={jest.fn()} />);
- expect(wrapper).toMatchSnapshot();
+it('should render with no suggestions', () => {
+ renderEmbedDocsPopup();
+
+ expect(screen.getAllByRole('link')).toHaveLength(5);
+ expect(screen.getByText('embed_docs.documentation')).toHaveFocus();
});
+
+it('should render with suggestions', () => {
+ renderEmbedDocsPopup([
+ { link: '/docs/awesome-doc', text: 'mindblowing' },
+ { link: '/docs/whocares', text: 'boring' }
+ ]);
+
+ expect(screen.getAllByRole('link')).toHaveLength(7);
+ expect(screen.getByText('mindblowing')).toHaveFocus();
+});
+
+function renderEmbedDocsPopup(suggestions: SuggestionLink[] = []) {
+ return renderComponent(
+ <SuggestionsContext.Provider
+ value={{ addSuggestions: jest.fn(), removeSuggestions: jest.fn(), suggestions }}>
+ <EmbedDocsPopup onClose={jest.fn()} />
+ </SuggestionsContext.Provider>
+ );
+}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<DropdownOverlay>
- <ContextConsumer>
- <Component />
- </ContextConsumer>
- <ul
- className="menu abs-width-240"
- role="group"
- >
- <li>
- <Link
- onClick={[MockFunction]}
- target="_blank"
- to="/documentation"
- >
- embed_docs.documentation
- </Link>
- </li>
- <li>
- <Link
- onClick={[MockFunction]}
- to="/web_api"
- >
- api_documentation.page
- </Link>
- </li>
- </ul>
- <ul
- className="menu abs-width-240"
- role="group"
- >
- <li>
- <a
- href="https://community.sonarsource.com/"
- rel="noopener noreferrer"
- target="_blank"
- >
- embed_docs.get_help
- </a>
- </li>
- </ul>
- <ul
- className="menu abs-width-240"
- role="group"
- >
- <li
- className="menu-header"
- role="presentation"
- >
- embed_docs.stay_connected
- </li>
- <li>
- <a
- href="https://www.sonarqube.org/whats-new/?referrer=sonarqube"
- rel="noopener noreferrer"
- target="_blank"
- >
- <img
- alt="embed_docs.news"
- className="spacer-right"
- height="18"
- src="/images/embed-doc/sq-icon.svg"
- width="18"
- />
- embed_docs.news
- </a>
- </li>
- <li>
- <a
- href="https://twitter.com/SonarQube"
- rel="noopener noreferrer"
- target="_blank"
- >
- <img
- alt="Twitter"
- className="spacer-right"
- height="18"
- src="/images/embed-doc/twitter-icon.svg"
- width="18"
- />
- Twitter
- </a>
- </li>
- </ul>
-</DropdownOverlay>
-`;