Merge branch 'gatsby-hooks' into 'gatsby'
Gatsby hooks See merge request javallone/regexper-static!43
This commit is contained in:
commit
5ffeb90616
4
src/__mocks__/react-i18next.js
vendored
4
src/__mocks__/react-i18next.js
vendored
@ -1,6 +1,8 @@
|
|||||||
const reactI18next = jest.requireActual('react-i18next');
|
const reactI18next = jest.requireActual('react-i18next');
|
||||||
|
const i18n = require('i18n');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...reactI18next,
|
...reactI18next,
|
||||||
Trans: require('__mocks__/component-mock').buildMock(reactI18next.Trans)
|
Trans: require('__mocks__/component-mock').buildMock(reactI18next.Trans),
|
||||||
|
useTranslation: () => ({ i18n, t: i18n.mockT })
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
exports[`App removing rendered expression 1`] = `
|
exports[`App removing rendered expression 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"js\\",
|
\\"syntax\\": \\"js\\",
|
||||||
\\"expr\\": \\"test expression\\",
|
\\"expr\\": \\"test expression\\",
|
||||||
@ -20,7 +20,7 @@ exports[`App removing rendered expression 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(FormActions)"
|
data-component="FormActions"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -36,7 +36,7 @@ exports[`App removing rendered expression 1`] = `
|
|||||||
exports[`App removing rendered expression 2`] = `
|
exports[`App removing rendered expression 2`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"js\\",
|
\\"syntax\\": \\"js\\",
|
||||||
\\"expr\\": \\"\\",
|
\\"expr\\": \\"\\",
|
||||||
@ -58,7 +58,7 @@ exports[`App removing rendered expression 2`] = `
|
|||||||
exports[`App rendering 1`] = `
|
exports[`App rendering 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"js\\",
|
\\"syntax\\": \\"js\\",
|
||||||
\\"expr\\": \\"\\",
|
\\"expr\\": \\"\\",
|
||||||
@ -80,7 +80,7 @@ exports[`App rendering 1`] = `
|
|||||||
exports[`App rendering an expression 1`] = `
|
exports[`App rendering an expression 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"js\\",
|
\\"syntax\\": \\"js\\",
|
||||||
\\"expr\\": \\"\\",
|
\\"expr\\": \\"\\",
|
||||||
@ -102,7 +102,7 @@ exports[`App rendering an expression 1`] = `
|
|||||||
exports[`App rendering an expression 2`] = `
|
exports[`App rendering an expression 2`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"js\\",
|
\\"syntax\\": \\"js\\",
|
||||||
\\"expr\\": \\"test expression\\",
|
\\"expr\\": \\"test expression\\",
|
||||||
@ -119,7 +119,7 @@ exports[`App rendering an expression 2`] = `
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Loader)"
|
data-component="Loader"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
@ -128,7 +128,7 @@ exports[`App rendering an expression 2`] = `
|
|||||||
exports[`App rendering an expression 3`] = `
|
exports[`App rendering an expression 3`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"js\\",
|
\\"syntax\\": \\"js\\",
|
||||||
\\"expr\\": \\"test expression\\",
|
\\"expr\\": \\"test expression\\",
|
||||||
@ -145,7 +145,7 @@ exports[`App rendering an expression 3`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(FormActions)"
|
data-component="FormActions"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -161,7 +161,7 @@ exports[`App rendering an expression 3`] = `
|
|||||||
exports[`App rendering with an invalid syntax 1`] = `
|
exports[`App rendering with an invalid syntax 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"invalid\\",
|
\\"syntax\\": \\"invalid\\",
|
||||||
\\"expr\\": \\"\\",
|
\\"expr\\": \\"\\",
|
||||||
@ -183,7 +183,7 @@ exports[`App rendering with an invalid syntax 1`] = `
|
|||||||
exports[`App rendering with an invalid syntax 2`] = `
|
exports[`App rendering with an invalid syntax 2`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"invalid\\",
|
\\"syntax\\": \\"invalid\\",
|
||||||
\\"expr\\": \\"test expression\\",
|
\\"expr\\": \\"test expression\\",
|
||||||
@ -200,7 +200,7 @@ exports[`App rendering with an invalid syntax 2`] = `
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Loader)"
|
data-component="Loader"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
@ -209,7 +209,7 @@ exports[`App rendering with an invalid syntax 2`] = `
|
|||||||
exports[`App rendering with an invalid syntax 3`] = `
|
exports[`App rendering with an invalid syntax 3`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Form)"
|
data-component="Form"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"syntax\\": \\"invalid\\",
|
\\"syntax\\": \\"invalid\\",
|
||||||
\\"expr\\": \\"test expression\\",
|
\\"expr\\": \\"test expression\\",
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
import ccLogo from './cc-by.svg';
|
import ccLogo from './cc-by.svg';
|
||||||
|
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
export const Footer = ({ t, buildId }) => (
|
export const Footer = ({ buildId }) => {
|
||||||
<footer className={ style.footer }>
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <footer className={ style.footer }>
|
||||||
<ul className={ style.list }>
|
<ul className={ style.list }>
|
||||||
<li>
|
<li>
|
||||||
<Trans>Created by <a
|
<Trans>Created by <a
|
||||||
@ -26,12 +28,11 @@ export const Footer = ({ t, buildId }) => (
|
|||||||
<div className={ style.buildId }>
|
<div className={ style.buildId }>
|
||||||
{ buildId }
|
{ buildId }
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>;
|
||||||
);
|
};
|
||||||
|
|
||||||
Footer.propTypes = {
|
Footer.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
buildId: PropTypes.string.isRequired
|
buildId: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Footer);
|
export default Footer;
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import Footer from 'components/Footer';
|
||||||
import { Footer } from 'components/Footer';
|
|
||||||
|
|
||||||
describe('Footer', () => {
|
describe('Footer', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Footer buildId="abc-123" t={ mockT } />
|
<Footer buildId="abc-123" />
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,86 +1,73 @@
|
|||||||
import React from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
import ExpandIcon from 'react-feather/dist/icons/chevrons-down';
|
import ExpandIcon from 'react-feather/dist/icons/chevrons-down';
|
||||||
|
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
class Form extends React.PureComponent {
|
const Form = ({ syntaxList, children, onSubmit, ...props }) => {
|
||||||
static propTypes = {
|
const { t } = useTranslation();
|
||||||
expr: PropTypes.string,
|
const [ expr, exprUpdate ] = useState(props.expr);
|
||||||
syntax: PropTypes.string,
|
const [ syntax, syntaxUpdate ] = useState(props.syntax);
|
||||||
syntaxList: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
label: PropTypes.string
|
|
||||||
})),
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
children: PropTypes.oneOfType([
|
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
|
||||||
PropTypes.node
|
|
||||||
]),
|
|
||||||
t: PropTypes.func.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
const handleExprChange = useCallback(event => {
|
||||||
expr: this.props.expr,
|
exprUpdate(event.target.value);
|
||||||
syntax: this.props.syntax
|
}, [exprUpdate]);
|
||||||
}
|
const handleSyntaxChange = useCallback(event => {
|
||||||
|
syntaxUpdate(event.target.value);
|
||||||
handleSubmit = event => {
|
}, [syntaxUpdate]);
|
||||||
|
const handleSubmit = useCallback(event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const { expr, syntax } = this.state;
|
onSubmit({ expr, syntax });
|
||||||
|
}, [expr, syntax, onSubmit]);
|
||||||
this.props.onSubmit({ expr, syntax });
|
const handleKeyPress = useCallback(event => {
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyPress = event => {
|
|
||||||
if (event.charCode === 13 && event.shiftKey) {
|
if (event.charCode === 13 && event.shiftKey) {
|
||||||
this.handleSubmit(event);
|
handleSubmit(event);
|
||||||
}
|
}
|
||||||
}
|
}, [handleSubmit]);
|
||||||
|
|
||||||
handleChange = event => this.setState({
|
return <div className={ style.form } data-requires-js>
|
||||||
[event.target.name]: event.target.value
|
<form data-testid="form" onSubmit={ handleSubmit }>
|
||||||
})
|
<textarea
|
||||||
|
data-testid="expr-input"
|
||||||
|
name="expr"
|
||||||
|
value={ expr }
|
||||||
|
onKeyPress={ handleKeyPress }
|
||||||
|
onChange={ handleExprChange }
|
||||||
|
autoFocus
|
||||||
|
placeholder={ t('Enter regular expression to display') }></textarea>
|
||||||
|
<button type="submit"><Trans>Display</Trans></button>
|
||||||
|
<div className={ style.select }>
|
||||||
|
<select
|
||||||
|
data-testid="syntax-select"
|
||||||
|
name="syntax"
|
||||||
|
value={ syntax }
|
||||||
|
onChange={ handleSyntaxChange } >
|
||||||
|
{ syntaxList.map(({ id, label }) => (
|
||||||
|
<option value={ id } key={ id }>{ t(label) }</option>
|
||||||
|
)) }
|
||||||
|
</select>
|
||||||
|
<ExpandIcon />
|
||||||
|
</div>
|
||||||
|
{ children }
|
||||||
|
</form>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
Form.propTypes = {
|
||||||
const {
|
expr: PropTypes.string,
|
||||||
syntaxList,
|
syntax: PropTypes.string,
|
||||||
children,
|
syntaxList: PropTypes.arrayOf(PropTypes.shape({
|
||||||
t
|
id: PropTypes.string,
|
||||||
} = this.props;
|
label: PropTypes.string
|
||||||
const { expr, syntax } = this.state;
|
})),
|
||||||
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
return <div className={ style.form } data-requires-js>
|
export default Form;
|
||||||
<form data-testid="form" onSubmit={ this.handleSubmit }>
|
|
||||||
<textarea
|
|
||||||
data-testid="expr-input"
|
|
||||||
name="expr"
|
|
||||||
value={ expr }
|
|
||||||
onKeyPress={ this.handleKeyPress }
|
|
||||||
onChange={ this.handleChange }
|
|
||||||
autoFocus
|
|
||||||
placeholder={ t('Enter regular expression to display') }></textarea>
|
|
||||||
<button type="submit"><Trans>Display</Trans></button>
|
|
||||||
<div className={ style.select }>
|
|
||||||
<select
|
|
||||||
data-testid="syntax-select"
|
|
||||||
name="syntax"
|
|
||||||
value={ syntax }
|
|
||||||
onChange={ this.handleChange } >
|
|
||||||
{ syntaxList.map(({ id, label }) => (
|
|
||||||
<option value={ id } key={ id }>{ t(label) }</option>
|
|
||||||
)) }
|
|
||||||
</select>
|
|
||||||
<ExpandIcon />
|
|
||||||
</div>
|
|
||||||
{ children }
|
|
||||||
</form>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Form };
|
|
||||||
export default withTranslation()(Form);
|
|
||||||
|
@ -5,14 +5,13 @@ jest.mock('react-feather/dist/icons/chevrons-down', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, fireEvent } from 'react-testing-library';
|
import { render, fireEvent } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import Form from 'components/Form';
|
||||||
import { Form } from 'components/Form';
|
|
||||||
|
|
||||||
const syntaxList = [
|
const syntaxList = [
|
||||||
{ id: 'testJS', label: 'Testing JS' },
|
{ id: 'testJS', label: 'Testing JS' },
|
||||||
{ id: 'other', label: 'Other' }
|
{ id: 'other', label: 'Other' }
|
||||||
];
|
];
|
||||||
const commonProps = { syntaxList, t: mockT };
|
const commonProps = { syntaxList };
|
||||||
|
|
||||||
describe('Form', () => {
|
describe('Form', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
|
@ -8,168 +8,6 @@ exports[`FormActions rendering 1`] = `
|
|||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`FormActions rendering download links 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<ul
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
download="image.png"
|
|
||||||
href="http://example.com/image.png"
|
|
||||||
type="image/png"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Download"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
TRANSLATE(Example PNG Link)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
download="image.svg"
|
|
||||||
href="http://example.com/image.svg"
|
|
||||||
type="image/svg+xml"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Download"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
TRANSLATE(Example SVG Link)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FormActions rendering download links with data after mounting 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<ul
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="http://example.com"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Link"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
data-component="Trans"
|
|
||||||
data-props="{}"
|
|
||||||
>
|
|
||||||
Permalink
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FormActions rendering download links with data after mounting 2`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<ul
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
download="image.png"
|
|
||||||
href="http://example.com/image.png"
|
|
||||||
type="image/png"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Download"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
TRANSLATE(Example PNG Link)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
download="image.svg"
|
|
||||||
href="http://example.com/image.svg"
|
|
||||||
type="image/svg+xml"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Download"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
TRANSLATE(Example SVG Link)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="http://example.com"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Link"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
data-component="Trans"
|
|
||||||
data-props="{}"
|
|
||||||
>
|
|
||||||
Permalink
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FormActions rendering download links with data after mounting 3`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<ul
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
download="image.png"
|
|
||||||
href="http://example.com/image.png"
|
|
||||||
type="image/png"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Download"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
TRANSLATE(Example PNG Link)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
download="image.svg"
|
|
||||||
href="http://example.com/image.svg"
|
|
||||||
type="image/svg+xml"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Download"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
TRANSLATE(Example SVG Link)
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="http://example.com"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Link"
|
|
||||||
data-props="{}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
data-component="Trans"
|
|
||||||
data-props="{}"
|
|
||||||
>
|
|
||||||
Permalink
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`FormActions rendering with a permalink 1`] = `
|
exports[`FormActions rendering with a permalink 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<ul
|
<ul
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
import DownloadIcon from 'react-feather/dist/icons/download';
|
import DownloadIcon from 'react-feather/dist/icons/download';
|
||||||
import LinkIcon from 'react-feather/dist/icons/link';
|
import LinkIcon from 'react-feather/dist/icons/link';
|
||||||
@ -9,89 +9,52 @@ import style from './style.module.css';
|
|||||||
|
|
||||||
import { createPngLink, createSvgLink } from './links';
|
import { createPngLink, createSvgLink } from './links';
|
||||||
|
|
||||||
class FormActions extends React.PureComponent {
|
const downloadLink = (link, t) => {
|
||||||
static propTypes = {
|
const { url, filename, type, label } = link;
|
||||||
permalinkUrl: PropTypes.string,
|
return <li>
|
||||||
imageDetails: PropTypes.shape({
|
<a href={ url } download={ filename } type={ type }>
|
||||||
svg: PropTypes.string,
|
<DownloadIcon />{ t(label) }
|
||||||
width: PropTypes.number,
|
</a>
|
||||||
height: PropTypes.number
|
</li>;
|
||||||
}),
|
};
|
||||||
t: PropTypes.func.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
const FormActions = ({
|
||||||
svgLink: null,
|
permalinkUrl,
|
||||||
pngLink: null
|
imageDetails
|
||||||
}
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [svgLink, setSvgLink] = useState(null);
|
||||||
|
const [pngLink, setPngLink] = useState(null);
|
||||||
|
|
||||||
componentDidMount() {
|
const generateDownloadLinks = useCallback(async () => {
|
||||||
const { imageDetails } = this.props;
|
const { svg, width, height } = imageDetails;
|
||||||
|
|
||||||
|
setSvgLink(await createSvgLink({ svg }));
|
||||||
|
setPngLink(await createPngLink({ svg, width, height }));
|
||||||
|
}, [setSvgLink, setPngLink, imageDetails]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (imageDetails && imageDetails.svg) {
|
if (imageDetails && imageDetails.svg) {
|
||||||
this.generateDownloadLinks();
|
generateDownloadLinks();
|
||||||
}
|
}
|
||||||
}
|
}, [imageDetails]);
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
return <ul className={ style.actions }>
|
||||||
const { imageDetails } = this.props;
|
{ pngLink && downloadLink(pngLink, t) }
|
||||||
const { imageDetails: prevImageDetails } = prevProps;
|
{ svgLink && downloadLink(svgLink, t) }
|
||||||
|
{ permalinkUrl && <li>
|
||||||
|
<a href={ permalinkUrl }><LinkIcon /><Trans>Permalink</Trans></a>
|
||||||
|
</li> }
|
||||||
|
</ul>;
|
||||||
|
};
|
||||||
|
|
||||||
if (!imageDetails) {
|
FormActions.propTypes = {
|
||||||
this.setState({ svgLink: null, pngLink: null });
|
permalinkUrl: PropTypes.string,
|
||||||
return;
|
imageDetails: PropTypes.shape({
|
||||||
}
|
svg: PropTypes.string,
|
||||||
|
width: PropTypes.number,
|
||||||
|
height: PropTypes.number
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
if (!prevImageDetails) {
|
export default FormActions;
|
||||||
this.generateDownloadLinks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageDetails.svg !== prevImageDetails.svg
|
|
||||||
|| imageDetails.width !== prevImageDetails.width
|
|
||||||
|| imageDetails.height !== prevImageDetails.height) {
|
|
||||||
this.generateDownloadLinks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateDownloadLinks() {
|
|
||||||
const { imageDetails: { svg, width, height } } = this.props;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
svgLink: await createSvgLink({ svg }),
|
|
||||||
pngLink: await createPngLink({ svg, width, height })
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLink({ url, filename, type, label }) {
|
|
||||||
const { t } = this.props;
|
|
||||||
|
|
||||||
return <li>
|
|
||||||
<a href={ url } download={ filename } type={ type }>
|
|
||||||
<DownloadIcon />{ t(label) }
|
|
||||||
</a>
|
|
||||||
</li>;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
permalinkUrl
|
|
||||||
} = this.props;
|
|
||||||
const {
|
|
||||||
svgLink,
|
|
||||||
pngLink
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return <ul className={ style.actions }>
|
|
||||||
{ pngLink && this.downloadLink(pngLink) }
|
|
||||||
{ svgLink && this.downloadLink(svgLink) }
|
|
||||||
{ permalinkUrl && <li>
|
|
||||||
<a href={ permalinkUrl }><LinkIcon /><Trans>Permalink</Trans></a>
|
|
||||||
</li> }
|
|
||||||
</ul>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { FormActions };
|
|
||||||
export default withTranslation()(FormActions);
|
|
||||||
|
@ -9,8 +9,7 @@ jest.mock('react-feather/dist/icons/link', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import FormActions from 'components/FormActions';
|
||||||
import { FormActions } from 'components/FormActions';
|
|
||||||
import { createPngLink, createSvgLink } from './links';
|
import { createPngLink, createSvgLink } from './links';
|
||||||
|
|
||||||
createPngLink.mockResolvedValue({
|
createPngLink.mockResolvedValue({
|
||||||
@ -29,68 +28,15 @@ createSvgLink.mockResolvedValue({
|
|||||||
describe('FormActions', () => {
|
describe('FormActions', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<FormActions t={ mockT } />
|
<FormActions/>
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rendering with a permalink', () => {
|
test('rendering with a permalink', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<FormActions permalinkUrl="http://example.com" t={ mockT } />
|
<FormActions permalinkUrl="http://example.com" />
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rendering download links', async () => {
|
|
||||||
const imageDetails = {
|
|
||||||
svg: 'test image',
|
|
||||||
width: 10,
|
|
||||||
height: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
const { asFragment } = render(
|
|
||||||
<FormActions imageDetails={ imageDetails } t={ mockT } />
|
|
||||||
);
|
|
||||||
|
|
||||||
// Give a beat for mocked promises to resolve
|
|
||||||
await new Promise(resolve => setTimeout(resolve));
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rendering download links with data after mounting', async () => {
|
|
||||||
const { asFragment, rerender } = render(
|
|
||||||
<FormActions t={ mockT } />
|
|
||||||
);
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<FormActions permalinkUrl="http://example.com" t={ mockT } />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<FormActions
|
|
||||||
permalinkUrl="http://example.com"
|
|
||||||
imageDetails={ { svg: 'test-image' } }
|
|
||||||
t={ mockT } />
|
|
||||||
);
|
|
||||||
|
|
||||||
// Give a beat for mocked promises to resolve
|
|
||||||
await new Promise(resolve => setTimeout(resolve));
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<FormActions
|
|
||||||
permalinkUrl="http://example.com"
|
|
||||||
imageDetails={ { svg: 'test-image', width: 10, height: 20 } }
|
|
||||||
t={ mockT } />
|
|
||||||
);
|
|
||||||
|
|
||||||
// Give a beat for mocked promises to resolve
|
|
||||||
await new Promise(resolve => setTimeout(resolve));
|
|
||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ exports[`Header opening the Privacy Policy modal 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -59,7 +59,7 @@ exports[`Header opening the Privacy Policy modal 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -67,7 +67,7 @@ exports[`Header opening the Privacy Policy modal 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -85,7 +85,7 @@ exports[`Header opening the Privacy Policy modal while holding alt key 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -135,7 +135,7 @@ exports[`Header opening the Privacy Policy modal while holding alt key 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -143,7 +143,7 @@ exports[`Header opening the Privacy Policy modal while holding alt key 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -161,7 +161,7 @@ exports[`Header opening the Privacy Policy modal while holding ctrl key 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -211,7 +211,7 @@ exports[`Header opening the Privacy Policy modal while holding ctrl key 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -219,7 +219,7 @@ exports[`Header opening the Privacy Policy modal while holding ctrl key 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -237,7 +237,7 @@ exports[`Header opening the Privacy Policy modal while holding meta key 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -287,7 +287,7 @@ exports[`Header opening the Privacy Policy modal while holding meta key 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -295,7 +295,7 @@ exports[`Header opening the Privacy Policy modal while holding meta key 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -313,7 +313,7 @@ exports[`Header opening the Privacy Policy modal while holding shift key 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -363,7 +363,7 @@ exports[`Header opening the Privacy Policy modal while holding shift key 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -371,7 +371,7 @@ exports[`Header opening the Privacy Policy modal while holding shift key 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -389,7 +389,7 @@ exports[`Header rendering 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -440,7 +440,7 @@ exports[`Header rendering 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -448,7 +448,7 @@ exports[`Header rendering 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -466,7 +466,7 @@ exports[`Header rendering with no banner 1`] = `
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -516,7 +516,7 @@ exports[`Header rendering with no banner 1`] = `
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(InstallPrompt)"
|
data-component="InstallPrompt"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
@ -524,7 +524,7 @@ exports[`Header rendering with no banner 1`] = `
|
|||||||
data-requires-js="true"
|
data-requires-js="true"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(LocaleSwitcher)"
|
data-component="LocaleSwitcher"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import { Link } from 'gatsby';
|
import { Link } from 'gatsby';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
import GitlabIcon from 'react-feather/dist/icons/gitlab';
|
import GitlabIcon from 'react-feather/dist/icons/gitlab';
|
||||||
|
|
||||||
@ -12,76 +12,66 @@ import PrivacyPolicy from 'components/PrivacyPolicy';
|
|||||||
|
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
class Header extends React.PureComponent {
|
const Header = ({ banner }) => {
|
||||||
state = {
|
const [ showModal, updateShowModal] = useState(false);
|
||||||
showModal: false
|
const handleClose = useCallback(() => {
|
||||||
}
|
updateShowModal(false);
|
||||||
|
}, [updateShowModal]);
|
||||||
static propTypes = {
|
const handleOpen = useCallback(event => {
|
||||||
banner: PropTypes.oneOfType([
|
|
||||||
PropTypes.bool,
|
|
||||||
PropTypes.string
|
|
||||||
]).isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOpen = event => {
|
|
||||||
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
|
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setState({ showModal: true });
|
updateShowModal(true);
|
||||||
}
|
}, [updateShowModal]);
|
||||||
|
|
||||||
handleClose = () => {
|
return <>
|
||||||
this.setState({ showModal: false });
|
<Modal
|
||||||
}
|
isOpen={ showModal }
|
||||||
|
onRequestClose={ handleClose }>
|
||||||
|
<PrivacyPolicy onClose={ handleClose } />
|
||||||
|
</Modal>
|
||||||
|
<header
|
||||||
|
className={ style.header }
|
||||||
|
data-banner={ banner || null }>
|
||||||
|
<h1>
|
||||||
|
<Link to="/">Regexper</Link>
|
||||||
|
</h1>
|
||||||
|
|
||||||
render() {
|
<ul className={ style.list }>
|
||||||
const { banner } = this.props;
|
<li>
|
||||||
const { showModal } = this.state;
|
<a href="https://gitlab.com/javallone/regexper-static"
|
||||||
|
rel="external noopener noreferrer"
|
||||||
|
target="_blank">
|
||||||
|
<GitlabIcon />
|
||||||
|
<Trans>Source on GitLab</Trans>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/privacy"
|
||||||
|
data-testid="privacy-link"
|
||||||
|
onClick={ handleOpen }
|
||||||
|
>
|
||||||
|
<Trans>Privacy Policy</Trans>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<InstallPrompt />
|
||||||
|
</li>
|
||||||
|
<li data-requires-js>
|
||||||
|
<LocaleSwitcher />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
return <>
|
Header.propTypes = {
|
||||||
<Modal
|
banner: PropTypes.oneOfType([
|
||||||
isOpen={ showModal }
|
PropTypes.bool,
|
||||||
onRequestClose={ this.handleClose }>
|
PropTypes.string
|
||||||
<PrivacyPolicy onClose={ this.handleClose } />
|
]).isRequired
|
||||||
</Modal>
|
};
|
||||||
<header
|
|
||||||
className={ style.header }
|
|
||||||
data-banner={ banner || null }>
|
|
||||||
<h1>
|
|
||||||
<Link to="/">Regexper</Link>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<ul className={ style.list }>
|
export default Header;
|
||||||
<li>
|
|
||||||
<a href="https://gitlab.com/javallone/regexper-static"
|
|
||||||
rel="external noopener noreferrer"
|
|
||||||
target="_blank">
|
|
||||||
<GitlabIcon />
|
|
||||||
<Trans>Source on GitLab</Trans>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to="/privacy"
|
|
||||||
data-testid="privacy-link"
|
|
||||||
onClick={ this.handleOpen }
|
|
||||||
>
|
|
||||||
<Trans>Privacy Policy</Trans>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<InstallPrompt />
|
|
||||||
</li>
|
|
||||||
<li data-requires-js>
|
|
||||||
<LocaleSwitcher />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Header };
|
|
||||||
export default withTranslation()(Header);
|
|
||||||
|
@ -12,7 +12,7 @@ jest.mock('components/PrivacyPolicy', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, fireEvent } from 'react-testing-library';
|
import { render, fireEvent } from 'react-testing-library';
|
||||||
|
|
||||||
import { Header } from 'components/Header';
|
import Header from 'components/Header';
|
||||||
|
|
||||||
describe('Header', () => {
|
describe('Header', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
|
@ -1,41 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`InstallPrompt accepting install prompt 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<a
|
|
||||||
data-testid="install"
|
|
||||||
href="#install"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Trans"
|
|
||||||
data-props="{}"
|
|
||||||
>
|
|
||||||
Add to Home Screen
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`InstallPrompt accepting install prompt 2`] = `<DocumentFragment />`;
|
|
||||||
|
|
||||||
exports[`InstallPrompt rejecting install prompt 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<a
|
|
||||||
data-testid="install"
|
|
||||||
href="#install"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-component="Trans"
|
|
||||||
data-props="{}"
|
|
||||||
>
|
|
||||||
Add to Home Screen
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`InstallPrompt rejecting install prompt 2`] = `<DocumentFragment />`;
|
|
||||||
|
|
||||||
exports[`InstallPrompt rendering 1`] = `<DocumentFragment />`;
|
exports[`InstallPrompt rendering 1`] = `<DocumentFragment />`;
|
||||||
|
|
||||||
exports[`InstallPrompt rendering after an install prompt has been requested 1`] = `<DocumentFragment />`;
|
exports[`InstallPrompt rendering after an install prompt has been requested 1`] = `<DocumentFragment />`;
|
||||||
|
@ -1,30 +1,12 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
class InstallPrompt extends React.PureComponent {
|
const InstallPrompt = () => {
|
||||||
state = {
|
const [ installPrompt, updateInstallPrompt ] = useState(null);
|
||||||
installPrompt: null
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
const handleInstall = useCallback(async event => {
|
||||||
window.addEventListener('beforeinstallprompt', this.handleInstallPrompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('beforeinstallprompt', this.handleInstallPrompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInstallPrompt = event => {
|
|
||||||
this.setState({
|
|
||||||
installPrompt: event
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInstall = async event => {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const { installPrompt } = this.state;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
installPrompt.prompt();
|
installPrompt.prompt();
|
||||||
await installPrompt.userChoice;
|
await installPrompt.userChoice;
|
||||||
@ -33,24 +15,27 @@ class InstallPrompt extends React.PureComponent {
|
|||||||
// User cancelled install
|
// User cancelled install
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ installPrompt: null });
|
updateInstallPrompt(null);
|
||||||
|
}, [installPrompt, updateInstallPrompt]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('beforeinstallprompt', updateInstallPrompt);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeinstallprompt', updateInstallPrompt);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!installPrompt) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
return <a href="#install"
|
||||||
const { installPrompt } = this.state;
|
data-testid="install"
|
||||||
|
onClick={ handleInstall }
|
||||||
|
>
|
||||||
|
<Trans>Add to Home Screen</Trans>
|
||||||
|
</a>;
|
||||||
|
};
|
||||||
|
|
||||||
if (!installPrompt) {
|
export default InstallPrompt;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <a href="#install"
|
|
||||||
data-testid="install"
|
|
||||||
onClick={ this.handleInstall }
|
|
||||||
>
|
|
||||||
<Trans>Add to Home Screen</Trans>
|
|
||||||
</a>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { InstallPrompt };
|
|
||||||
export default withTranslation()(InstallPrompt);
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, fireEvent } from 'react-testing-library';
|
import { render, fireEvent } from 'react-testing-library';
|
||||||
|
|
||||||
import { InstallPrompt } from 'components/InstallPrompt';
|
import InstallPrompt from 'components/InstallPrompt';
|
||||||
|
|
||||||
describe('InstallPrompt', () => {
|
describe('InstallPrompt', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
@ -39,48 +39,4 @@ describe('InstallPrompt', () => {
|
|||||||
'beforeinstallprompt',
|
'beforeinstallprompt',
|
||||||
expect.any(Function));
|
expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('accepting install prompt', async () => {
|
|
||||||
const { asFragment, getByTestId } = render(
|
|
||||||
<InstallPrompt />
|
|
||||||
);
|
|
||||||
const promptEvent = new Event('beforeinstallprompt');
|
|
||||||
promptEvent.prompt = jest.fn();
|
|
||||||
promptEvent.userChoice = Promise.resolve();
|
|
||||||
const clickEvent = new MouseEvent('click', { bubbles: true });
|
|
||||||
jest.spyOn(clickEvent, 'preventDefault');
|
|
||||||
|
|
||||||
fireEvent(window, promptEvent);
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
fireEvent(getByTestId('install'), clickEvent);
|
|
||||||
|
|
||||||
// Allow async code to run
|
|
||||||
await new Promise(resolve => setTimeout(resolve));
|
|
||||||
|
|
||||||
expect(clickEvent.preventDefault).toHaveBeenCalled();
|
|
||||||
expect(promptEvent.prompt).toHaveBeenCalled();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rejecting install prompt', async () => {
|
|
||||||
const { asFragment, getByTestId } = render(
|
|
||||||
<InstallPrompt />
|
|
||||||
);
|
|
||||||
const promptEvent = new Event('beforeinstallprompt');
|
|
||||||
promptEvent.prompt = jest.fn();
|
|
||||||
promptEvent.userChoice = Promise.reject();
|
|
||||||
const clickEvent = new MouseEvent('click', { bubbles: true });
|
|
||||||
jest.spyOn(clickEvent, 'preventDefault');
|
|
||||||
|
|
||||||
fireEvent(window, promptEvent);
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
fireEvent(getByTestId('install'), clickEvent);
|
|
||||||
|
|
||||||
// Allow async code to run
|
|
||||||
await new Promise(resolve => setTimeout(resolve));
|
|
||||||
|
|
||||||
expect(clickEvent.preventDefault).toHaveBeenCalled();
|
|
||||||
expect(promptEvent.prompt).toHaveBeenCalled();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,7 @@ exports[`Layout rendering 1`] = `
|
|||||||
>
|
>
|
||||||
<noscript />
|
<noscript />
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Header)"
|
data-component="Header"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"banner\\": \\"Test Banner\\"
|
\\"banner\\": \\"Test Banner\\"
|
||||||
}"
|
}"
|
||||||
@ -20,7 +20,7 @@ exports[`Layout rendering 1`] = `
|
|||||||
Example content
|
Example content
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Footer)"
|
data-component="Footer"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"buildId\\": \\"test-buildid\\"
|
\\"buildId\\": \\"test-buildid\\"
|
||||||
}"
|
}"
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import LoaderIcon from 'react-feather/dist/icons/loader';
|
import LoaderIcon from 'react-feather/dist/icons/loader';
|
||||||
|
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
const Loader = ({ t }) => (
|
const Loader = () => {
|
||||||
<div className={ style.loader }>
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <div className={ style.loader }>
|
||||||
<LoaderIcon />
|
<LoaderIcon />
|
||||||
<div className={ style.message }>{ t('Loading...') }</div>
|
<div className={ style.message }>{ t('Loading...') }</div>
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
|
|
||||||
Loader.propTypes = {
|
|
||||||
t: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Loader };
|
export default Loader;
|
||||||
export default withTranslation()(Loader);
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import Loader from 'components/Loader';
|
||||||
import { Loader } from 'components/Loader';
|
|
||||||
|
|
||||||
describe('Loader', () => {
|
describe('Loader', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
// Using full rendering here since styles for this depend on the structure
|
// Using full rendering here since styles for this depend on the structure
|
||||||
// of the SVG.
|
// of the SVG.
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Loader t={ mockT } />
|
<Loader/>
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
import ExpandIcon from 'react-feather/dist/icons/chevrons-down';
|
import ExpandIcon from 'react-feather/dist/icons/chevrons-down';
|
||||||
|
|
||||||
@ -8,50 +8,40 @@ import i18n, { locales } from 'i18n';
|
|||||||
import localeToAvailable from './locale-to-available';
|
import localeToAvailable from './locale-to-available';
|
||||||
import style from './style.module.css';
|
import style from './style.module.css';
|
||||||
|
|
||||||
export class LocaleSwitcher extends React.PureComponent {
|
const LocaleSwitcher = () => {
|
||||||
state = {
|
const [ current, updateCurrent ] = useState(localeToAvailable(
|
||||||
current: localeToAvailable(
|
i18n.language || '',
|
||||||
i18n.language || '',
|
locales.map(l => l.code),
|
||||||
locales.map(l => l.code),
|
'en'));
|
||||||
'en')
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
i18n.on('languageChanged', this.handleLanguageChange);
|
i18n.on('languageChanged', updateCurrent);
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
return () => {
|
||||||
i18n.off('languageChanged', this.handleLanguageChange);
|
i18n.off('languageChanged', updateCurrent);
|
||||||
}
|
};
|
||||||
|
});
|
||||||
|
|
||||||
handleSelectChange = ({ target }) => {
|
const handleSelectChange = useCallback(({ target }) => {
|
||||||
i18n.changeLanguage(target.value);
|
i18n.changeLanguage(target.value);
|
||||||
}
|
});
|
||||||
|
|
||||||
handleLanguageChange = lang => {
|
return <label>
|
||||||
this.setState({ current: lang });
|
<Trans>Language</Trans>
|
||||||
}
|
<div className={ style.switcher }>
|
||||||
|
<select data-testid="language-select"
|
||||||
|
value={ current }
|
||||||
|
onChange={ handleSelectChange }
|
||||||
|
>
|
||||||
|
{ locales.map(locale => (
|
||||||
|
<option value={ locale.code } key={ locale.code }>
|
||||||
|
{ locale.name }
|
||||||
|
</option>
|
||||||
|
)) }
|
||||||
|
</select>
|
||||||
|
<ExpandIcon />
|
||||||
|
</div>
|
||||||
|
</label>;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
export default LocaleSwitcher;
|
||||||
const { current } = this.state;
|
|
||||||
|
|
||||||
return <label>
|
|
||||||
<Trans>Language</Trans>
|
|
||||||
<div className={ style.switcher }>
|
|
||||||
<select data-testid="language-select"
|
|
||||||
value={ current }
|
|
||||||
onChange={ this.handleSelectChange }
|
|
||||||
>
|
|
||||||
{ locales.map(locale => (
|
|
||||||
<option value={ locale.code } key={ locale.code }>
|
|
||||||
{ locale.name }
|
|
||||||
</option>
|
|
||||||
)) }
|
|
||||||
</select>
|
|
||||||
<ExpandIcon />
|
|
||||||
</div>
|
|
||||||
</label>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation()(LocaleSwitcher);
|
|
||||||
|
@ -3,10 +3,10 @@ jest.mock('react-feather/dist/icons/chevrons-down', () =>
|
|||||||
'react-feather/dist/icons/chevrons-down'));
|
'react-feather/dist/icons/chevrons-down'));
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, fireEvent } from 'react-testing-library';
|
import { render, fireEvent, act } from 'react-testing-library';
|
||||||
|
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
import { LocaleSwitcher } from 'components/LocaleSwitcher';
|
import LocaleSwitcher from 'components/LocaleSwitcher';
|
||||||
|
|
||||||
// Ensure initial locale is always "en" during tests
|
// Ensure initial locale is always "en" during tests
|
||||||
jest.mock('./locale-to-available', () => jest.fn(() => 'en'));
|
jest.mock('./locale-to-available', () => jest.fn(() => 'en'));
|
||||||
@ -40,7 +40,11 @@ describe('LocaleSwitcher', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(getByTestId('language-select').value).toEqual('en');
|
expect(getByTestId('language-select').value).toEqual('en');
|
||||||
i18n.emit('languageChanged', 'other');
|
|
||||||
|
act(() => {
|
||||||
|
i18n.emit('languageChanged', 'other');
|
||||||
|
});
|
||||||
|
|
||||||
expect(getByTestId('language-select').value).toEqual('other');
|
expect(getByTestId('language-select').value).toEqual('other');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,15 +5,15 @@ exports[`Metadata rendering 1`] = `
|
|||||||
<span
|
<span
|
||||||
data-component="HelmetWrapper"
|
data-component="HelmetWrapper"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"htmlAttributes\\": {
|
\\"title\\": \\"Regexper\\",
|
||||||
\\"lang\\": \\"test-lang\\"
|
\\"htmlAttributes\\": {},
|
||||||
}
|
\\"meta\\": [
|
||||||
|
{
|
||||||
|
\\"name\\": \\"description\\"
|
||||||
|
}
|
||||||
|
]
|
||||||
}"
|
}"
|
||||||
>
|
/>
|
||||||
<title>
|
|
||||||
Regexper
|
|
||||||
</title>
|
|
||||||
</span>
|
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -22,18 +22,15 @@ exports[`Metadata rendering with a title and description 1`] = `
|
|||||||
<span
|
<span
|
||||||
data-component="HelmetWrapper"
|
data-component="HelmetWrapper"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"htmlAttributes\\": {
|
\\"title\\": \\"Regexper - Testing\\",
|
||||||
\\"lang\\": \\"test-lang\\"
|
\\"htmlAttributes\\": {},
|
||||||
}
|
\\"meta\\": [
|
||||||
|
{
|
||||||
|
\\"name\\": \\"description\\",
|
||||||
|
\\"content\\": \\"Test description\\"
|
||||||
|
}
|
||||||
|
]
|
||||||
}"
|
}"
|
||||||
>
|
/>
|
||||||
<title>
|
|
||||||
Regexper - Testing
|
|
||||||
</title>
|
|
||||||
<meta
|
|
||||||
content="Test description"
|
|
||||||
name="description"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
class Metadata extends React.PureComponent {
|
const Metadata = ({ title, description }) => {
|
||||||
static propTypes = {
|
const { i18n } = useTranslation();
|
||||||
title: PropTypes.string,
|
const helmetProps = {
|
||||||
description: PropTypes.string,
|
title: title ? `Regexper - ${ title }` : 'Regexper',
|
||||||
i18n: PropTypes.shape({
|
htmlAttributes: {
|
||||||
language: PropTypes.string.isRequired
|
|
||||||
}).isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { title, description, i18n } = this.props;
|
|
||||||
const htmlAttributes = {
|
|
||||||
lang: i18n.language
|
lang: i18n.language
|
||||||
};
|
},
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
content: description
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
return <Helmet htmlAttributes={ htmlAttributes }>
|
return <Helmet { ...helmetProps }></Helmet>;
|
||||||
<title>{ title ? `Regexper - ${ title }` : 'Regexper' }</title>
|
};
|
||||||
{ description && <meta name="description" content={ description } /> }
|
|
||||||
</Helmet>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Metadata };
|
Metadata.propTypes = {
|
||||||
export default withTranslation()(Metadata);
|
title: PropTypes.string,
|
||||||
|
description: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Metadata;
|
||||||
|
@ -9,16 +9,12 @@ jest.mock('react-helmet', () => {
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { Metadata } from 'components/Metadata';
|
import Metadata from 'components/Metadata';
|
||||||
|
|
||||||
const commonProps = {
|
|
||||||
i18n: { language: 'test-lang' }
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Metadata', () => {
|
describe('Metadata', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Metadata { ...commonProps } />
|
<Metadata/>
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@ -27,8 +23,7 @@ describe('Metadata', () => {
|
|||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Metadata
|
<Metadata
|
||||||
title="Testing"
|
title="Testing"
|
||||||
description="Test description"
|
description="Test description" />
|
||||||
{ ...commonProps } />
|
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
|
||||||
|
|
||||||
import Message from 'components/Message';
|
import Message from 'components/Message';
|
||||||
|
|
||||||
export const PrivacyPolicy = ({ t, ...props }) => (
|
export const PrivacyPolicy = props => {
|
||||||
<Message type="info" heading={ t('Privacy Policy') } { ...props }>
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <Message type="info" heading={ t('Privacy Policy') } { ...props }>
|
||||||
<Trans i18nKey="Privacy policy copy">
|
<Trans i18nKey="Privacy policy copy">
|
||||||
<p>
|
<p>
|
||||||
Regexper and the tools used to create it are all open source. If you are
|
Regexper and the tools used to create it are all open source. If you are
|
||||||
@ -44,11 +45,7 @@ export const PrivacyPolicy = ({ t, ...props }) => (
|
|||||||
Regexper is not supported by ad revenue or sales of any kind.
|
Regexper is not supported by ad revenue or sales of any kind.
|
||||||
</p>
|
</p>
|
||||||
</Trans>
|
</Trans>
|
||||||
</Message>
|
</Message>;
|
||||||
);
|
|
||||||
|
|
||||||
PrivacyPolicy.propTypes = {
|
|
||||||
t: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(PrivacyPolicy);
|
export default PrivacyPolicy;
|
||||||
|
@ -4,13 +4,12 @@ jest.mock('components/Message', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import PrivacyPolicy from 'components/PrivacyPolicy';
|
||||||
import { PrivacyPolicy } from 'components/PrivacyPolicy';
|
|
||||||
|
|
||||||
describe('PrivacyPolicy', () => {
|
describe('PrivacyPolicy', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<PrivacyPolicy onClose={ jest.fn() } t={ mockT } />
|
<PrivacyPolicy onClose={ jest.fn() } />
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useRef, useCallback, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import nodeTypes from 'rendering/types';
|
import nodeTypes from 'rendering/types';
|
||||||
@ -40,48 +40,40 @@ const render = (data, key) => {
|
|||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Render extends React.PureComponent {
|
const Render = ({ data, onRender }) => {
|
||||||
static propTypes = {
|
const svgContainer = useRef();
|
||||||
data: PropTypes.object.isRequired,
|
|
||||||
onRender: PropTypes.func.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
svgContainer = React.createRef()
|
const provideSVGData = useCallback(() => {
|
||||||
|
if (!svgContainer.current) {
|
||||||
componentDidMount() {
|
|
||||||
this.provideSVGData();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.provideSVGData();
|
|
||||||
}
|
|
||||||
|
|
||||||
provideSVGData() {
|
|
||||||
if (!this.svgContainer.current) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const svg = this.svgContainer.current.querySelector('svg');
|
const svg = svgContainer.current.querySelector('svg');
|
||||||
this.props.onRender({
|
onRender({
|
||||||
svg: svg.outerHTML,
|
svg: svg.outerHTML,
|
||||||
width: Number(svg.getAttribute('width')),
|
width: Number(svg.getAttribute('width')),
|
||||||
height: Number(svg.getAttribute('height'))
|
height: Number(svg.getAttribute('height'))
|
||||||
});
|
});
|
||||||
}
|
}, [svgContainer, onRender]);
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
const { data } = this.props;
|
provideSVGData();
|
||||||
|
}, [provideSVGData]);
|
||||||
|
|
||||||
return <div className={ style.render } ref={ this.svgContainer }>
|
return <div className={ style.render } ref={ svgContainer }>
|
||||||
{ render({
|
{ render({
|
||||||
...data,
|
...data,
|
||||||
props: {
|
props: {
|
||||||
...data.props,
|
...data.props,
|
||||||
onReflow: this.provideSVGData
|
onReflow: provideSVGData
|
||||||
}
|
}
|
||||||
}) }
|
}) }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
Render.propTypes = {
|
||||||
|
data: PropTypes.object.isRequired,
|
||||||
|
onRender: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
export default Render;
|
export default Render;
|
||||||
|
@ -9,7 +9,7 @@ exports[`SentryBoundary error handling 1`] = `
|
|||||||
exports[`SentryBoundary error handling 2`] = `
|
exports[`SentryBoundary error handling 2`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(SentryError)"
|
data-component="SentryError"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as Sentry from '@sentry/browser';
|
import * as Sentry from '@sentry/browser';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
import Message from 'components/Message';
|
import Message from 'components/Message';
|
||||||
|
|
||||||
@ -13,19 +12,17 @@ const reportError = event => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SentryError = ({ t }) => (
|
export const SentryError = () => {
|
||||||
<Message type="error" heading={ t('An error has occurred') }>
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <Message type="error" heading={ t('An error has occurred') }>
|
||||||
<p>
|
<p>
|
||||||
<Trans>This error has been logged. You may also <a
|
<Trans>This error has been logged. You may also <a
|
||||||
href="#error-report"
|
href="#error-report"
|
||||||
data-testid="error-report"
|
data-testid="error-report"
|
||||||
onClick={ reportError }>fill out a report</a>.</Trans>
|
onClick={ reportError }>fill out a report</a>.</Trans>
|
||||||
</p>
|
</p>
|
||||||
</Message>
|
</Message>;
|
||||||
);
|
|
||||||
|
|
||||||
SentryError.propTypes = {
|
|
||||||
t: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(SentryError);
|
export default SentryError;
|
||||||
|
@ -7,13 +7,12 @@ import React from 'react';
|
|||||||
import { render, fireEvent } from 'react-testing-library';
|
import { render, fireEvent } from 'react-testing-library';
|
||||||
import * as Sentry from '@sentry/browser';
|
import * as Sentry from '@sentry/browser';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import SentryError from 'components/SentryError';
|
||||||
import { SentryError } from 'components/SentryError';
|
|
||||||
|
|
||||||
describe('SentryError', () => {
|
describe('SentryError', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<SentryError t={ mockT }/>
|
<SentryError/>
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@ -22,7 +21,7 @@ describe('SentryError', () => {
|
|||||||
test('fill out a report when an event has been logged', () => {
|
test('fill out a report when an event has been logged', () => {
|
||||||
Sentry.lastEventId.mockReturnValue(1);
|
Sentry.lastEventId.mockReturnValue(1);
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<SentryError t={ mockT } />
|
<SentryError/>
|
||||||
);
|
);
|
||||||
const event = new MouseEvent('click', { bubbles: true });
|
const event = new MouseEvent('click', { bubbles: true });
|
||||||
jest.spyOn(event, 'preventDefault');
|
jest.spyOn(event, 'preventDefault');
|
||||||
@ -35,7 +34,7 @@ describe('SentryError', () => {
|
|||||||
test('fill out a report when an event has not been logged', () => {
|
test('fill out a report when an event has not been logged', () => {
|
||||||
Sentry.lastEventId.mockReturnValue(false);
|
Sentry.lastEventId.mockReturnValue(false);
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<SentryError t={ mockT } />
|
<SentryError/>
|
||||||
);
|
);
|
||||||
const event = new MouseEvent('click', { bubbles: true });
|
const event = new MouseEvent('click', { bubbles: true });
|
||||||
jest.spyOn(event, 'preventDefault');
|
jest.spyOn(event, 'preventDefault');
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { withTranslation, Trans } from 'react-i18next';
|
|
||||||
|
|
||||||
import Metadata from 'components/Metadata';
|
import Metadata from 'components/Metadata';
|
||||||
import Message from 'components/Message';
|
import Message from 'components/Message';
|
||||||
|
|
||||||
export const ErrorPage = ({ t }) => <>
|
export const ErrorPage = () => {
|
||||||
<Metadata title={ t('Page Not Found') } />
|
const { t } = useTranslation();
|
||||||
<Message type="error" heading={ t('404 Page Not Found') }>
|
|
||||||
<p><Trans>The page you have requested could not be found.</Trans></p>
|
|
||||||
</Message>
|
|
||||||
</>;
|
|
||||||
|
|
||||||
ErrorPage.propTypes = {
|
return <>
|
||||||
t: PropTypes.func.isRequired
|
<Metadata title={ t('Page Not Found') } />
|
||||||
|
<Message type="error" heading={ t('404 Page Not Found') }>
|
||||||
|
<p><Trans>The page you have requested could not be found.</Trans></p>
|
||||||
|
</Message>
|
||||||
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(ErrorPage);
|
export default ErrorPage;
|
||||||
|
@ -6,13 +6,12 @@ jest.mock('components/Message', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import ErrorPage from 'pages/404';
|
||||||
import { ErrorPage } from 'pages/404';
|
|
||||||
|
|
||||||
describe('Error Page', () => {
|
describe('Error Page', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<ErrorPage t={ mockT } />
|
<ErrorPage/>
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
exports[`Error Page rendering 1`] = `
|
exports[`Error Page rendering 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Metadata)"
|
data-component="Metadata"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"title\\": \\"TRANSLATE(Page Not Found)\\"
|
\\"title\\": \\"TRANSLATE(Page Not Found)\\"
|
||||||
}"
|
}"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
exports[`Index Page rendering 1`] = `
|
exports[`Index Page rendering 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Metadata)"
|
data-component="Metadata"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"description\\": \\"Test description\\"
|
\\"description\\": \\"Test description\\"
|
||||||
}"
|
}"
|
||||||
@ -33,7 +33,7 @@ exports[`Index Page rendering 1`] = `
|
|||||||
exports[`Index Page rendering with an expression on the URL 1`] = `
|
exports[`Index Page rendering with an expression on the URL 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Metadata)"
|
data-component="Metadata"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"description\\": \\"Test description\\"
|
\\"description\\": \\"Test description\\"
|
||||||
}"
|
}"
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
exports[`Privacy Page rendering 1`] = `
|
exports[`Privacy Page rendering 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(Metadata)"
|
data-component="Metadata"
|
||||||
data-props="{
|
data-props="{
|
||||||
\\"title\\": \\"TRANSLATE(Privacy Policy)\\"
|
\\"title\\": \\"TRANSLATE(Privacy Policy)\\"
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
data-component="withI18nextTranslation(PrivacyPolicy)"
|
data-component="PrivacyPolicy"
|
||||||
data-props="{}"
|
data-props="{}"
|
||||||
/>
|
/>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
|
@ -8,7 +8,7 @@ jest.mock('components/App', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { IndexPage } from 'pages/index';
|
import IndexPage from 'pages/index';
|
||||||
|
|
||||||
const queryResult = {
|
const queryResult = {
|
||||||
site: {
|
site: {
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import Metadata from 'components/Metadata';
|
import Metadata from 'components/Metadata';
|
||||||
import PrivacyPolicy from 'components/PrivacyPolicy';
|
import PrivacyPolicy from 'components/PrivacyPolicy';
|
||||||
|
|
||||||
export const PrivacyPage = ({ t }) => <>
|
export const PrivacyPage = () => {
|
||||||
<Metadata title={ t('Privacy Policy') } />
|
const { t } = useTranslation();
|
||||||
<PrivacyPolicy />
|
|
||||||
</>;
|
|
||||||
|
|
||||||
PrivacyPage.propTypes = {
|
return <>
|
||||||
t: PropTypes.func.isRequired
|
<Metadata title={ t('Privacy Policy') } />
|
||||||
|
<PrivacyPolicy />
|
||||||
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(PrivacyPage);
|
export default PrivacyPage;
|
||||||
|
@ -6,13 +6,12 @@ jest.mock('components/PrivacyPolicy', () =>
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-testing-library';
|
import { render } from 'react-testing-library';
|
||||||
|
|
||||||
import { mockT } from 'i18n';
|
import PrivacyPage from 'pages/privacy';
|
||||||
import { PrivacyPage } from 'pages/privacy';
|
|
||||||
|
|
||||||
describe('Privacy Page', () => {
|
describe('Privacy Page', () => {
|
||||||
test('rendering', () => {
|
test('rendering', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<PrivacyPage t={ mockT } />
|
<PrivacyPage/>
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user