}
.a11y-hidden {
- position: absolute;
- left: -10000px;
- top: auto;
- width: 1px;
- height: 1px;
- overflow: hidden;
+ position: absolute !important;
+ left: -10000px !important;
+ top: auto !important;
+ width: 1px !important;
+ height: 1px !important;
+ overflow: hidden !important;
}
.invisible {
import * as React from 'react';
import { getProfileChangelog } from '../../../api/quality-profiles';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
+import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { parseDate, toShortNotSoISOString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { withQualityProfilesContext } from '../qualityProfilesContext';
onReset={this.handleReset}
/>
- {this.state.loading && <i className="spinner spacer-left" />}
+ <DeferredSpinner loading={this.state.loading} className="spacer-left" />
</header>
{this.state.events != null && this.state.events.length === 0 && <ChangelogEmpty />}
onDateRangeChange={[Function]}
onReset={[Function]}
/>
+ <DeferredSpinner
+ className="spacer-left"
+ loading={false}
+ />
</header>
<Changelog
events={
onDateRangeChange={[Function]}
onReset={[Function]}
/>
+ <DeferredSpinner
+ className="spacer-left"
+ loading={false}
+ />
</header>
<ChangelogEmpty />
</div>
it('should render correctly when loading', async () => {
renderActivityGraph({ loading: true });
- expect(await screen.findByLabelText('loading')).toBeInTheDocument();
+ expect(await screen.findByText('loading')).toBeInTheDocument();
});
it('should show the correct legend items', async () => {
: translateWithParameters('x_show', formatMeasure(count, 'INT', null))}
</span>
{button}
- {loading && <DeferredSpinner className="text-bottom spacer-left position-absolute" />}
+ {<DeferredSpinner loading={loading} className="text-bottom spacer-left position-absolute" />}
</div>
);
}
jest.useFakeTimers();
renderCheckbox({ label: 'me', children, loading: true });
jest.runAllTimers();
- expect(screen.getByLabelText('me')).toMatchSnapshot();
+ expect(screen.getByTestId('deferred-spinner')).toMatchSnapshot();
jest.useRealTimers();
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Checkbox with children should render loading state 1`] = `
-<a
- aria-checked="true"
- aria-label="me"
- class="link-checkbox"
- href="#"
- role="checkbox"
+<i
+ aria-live="polite"
+ class="deferred-spinner is-loading"
+ data-testid="deferred-spinner"
>
- <i
- class="deferred-spinner"
- />
- <a>
- child
- </a>
-</a>
+ <span
+ class="a11y-hidden"
+ >
+ loading
+ </span>
+</i>
`;
exports[`Checkbox with no children should render loading state 1`] = `
<i
- aria-label="me"
aria-live="polite"
- class="deferred-spinner"
-/>
+ class="deferred-spinner is-loading"
+ data-testid="deferred-spinner"
+>
+ <span
+ class="a11y-hidden"
+ >
+ me
+ </span>
+</i>
`;
exports[`should render the checkbox on the right 1`] = `
<a>
child
</a>
+ <i
+ aria-live="polite"
+ class="deferred-spinner a11y-hidden"
+ data-testid="deferred-spinner"
+ />
<i
class="icon-checkbox icon-checkbox-checked"
/>
>
show_more
</Button>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
</div>
`;
>
x_of_y_shown.5.5
</span>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
</div>
`;
>
x_of_y_shown.3.5
</span>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
</div>
`;
>
show_more
</Button>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
</div>
`;
</Button>
<DeferredSpinner
className="text-bottom spacer-left position-absolute"
+ loading={true}
/>
</div>
`;
>
reload
</Button>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
</div>
`;
</Button>
<DeferredSpinner
className="text-bottom spacer-left position-absolute"
+ loading={true}
/>
</div>
`;
>
x_show.3
</span>
+ <DeferredSpinner
+ className="text-bottom spacer-left position-absolute"
+ />
</div>
`;
*/
import classNames from 'classnames';
import * as React from 'react';
+import { translate } from '../../helpers/l10n';
import './DeferredSpinner.css';
interface Props {
className?: string;
customSpinner?: JSX.Element;
loading?: boolean;
- placeholder?: boolean;
timeout?: number;
}
const DEFAULT_TIMEOUT = 100;
+/**
+ * Recommendation: do not render this component conditionally based on any loading state:
+ * // Don't do this:
+ * {loading && <DeferredSpinner />}
+ * Instead, pass the loading state as a prop:
+ * // Do this:
+ * <DeferredSpinner loading={loading} />
+ */
export default class DeferredSpinner extends React.PureComponent<Props, State> {
timer?: number;
};
render() {
- const { ariaLabel, children, className, customSpinner, placeholder } = this.props;
- if (this.state.showSpinner) {
- return (
- customSpinner || (
- <i
- className={classNames('deferred-spinner', className)}
- aria-live={ariaLabel ? 'polite' : undefined}
- aria-label={ariaLabel}
- />
- )
- );
+ const { ariaLabel = translate('loading'), children, className, customSpinner } = this.props;
+ const { showSpinner } = this.state;
+
+ if (customSpinner) {
+ return showSpinner ? customSpinner : children;
}
+
return (
- children ||
- (placeholder ? (
+ <>
<i
- data-testid="deferred-spinner-placeholder"
- className={classNames('deferred-spinner-placeholder', className)}
- aria-live={ariaLabel ? 'polite' : undefined}
- aria-label={ariaLabel}
- />
- ) : null)
+ aria-live="polite"
+ data-testid="deferred-spinner"
+ className={classNames('deferred-spinner', className, {
+ 'a11y-hidden': !showSpinner,
+ 'is-loading': showSpinner,
+ })}
+ >
+ {showSpinner && <span className="a11y-hidden">{ariaLabel}</span>}
+ </i>
+ {!showSpinner && children}
+ </>
);
}
}
it('renders spinner after timeout', () => {
renderDeferredSpinner();
- expect(screen.queryByLabelText('loading')).not.toBeInTheDocument();
+ expect(screen.queryByText('loading')).not.toBeInTheDocument();
jest.runAllTimers();
- expect(screen.getByLabelText('loading')).toBeInTheDocument();
-});
-
-it('renders a placeholder while waiting', () => {
- renderDeferredSpinner({ placeholder: true });
- expect(screen.getByTestId('deferred-spinner-placeholder')).toBeInTheDocument();
+ expect(screen.getByText('loading')).toBeInTheDocument();
});
it('allows setting a custom class name', () => {
renderDeferredSpinner({ className: 'foo' });
jest.runAllTimers();
- expect(screen.getByLabelText('loading')).toHaveClass('foo');
+ expect(screen.getByTestId('deferred-spinner')).toHaveClass('foo');
});
it('can be controlled by the loading prop', () => {
const { rerender } = renderDeferredSpinner({ loading: true });
jest.runAllTimers();
- expect(screen.getByLabelText('loading')).toBeInTheDocument();
+ expect(screen.getByText('loading')).toBeInTheDocument();
rerender(prepareDeferredSpinner({ loading: false }));
- expect(screen.queryByLabelText('loading')).not.toBeInTheDocument();
+ expect(screen.queryByText('loading')).not.toBeInTheDocument();
});
function renderDeferredSpinner(props: Partial<DeferredSpinner['props']> = {}) {