Commit a40fec0b authored by dengxiaofeng's avatar dengxiaofeng

init commit

parent 4e366050
{
"projectName": "react-boilerplate",
"projectOwner": "react-boilerplate",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 80,
"commit": true,
"contributors": [
{
"login": "mxstbr",
"name": "Max Stoiber",
"avatar_url": "https://avatars0.githubusercontent.com/u/7525670?v=4",
"profile": "https://mxstbr.com",
"contributions": [
"code",
"doc",
"ideas",
"review",
"test"
]
},
{
"login": "julienben",
"name": "Julien Benchetrit",
"avatar_url": "https://avatars2.githubusercontent.com/u/8948127?v=4",
"profile": "https://julien.engineering/",
"contributions": [
"code",
"question",
"doc",
"review",
"maintenance"
]
},
{
"login": "gretzky",
"name": "Sara Federico",
"avatar_url": "https://avatars1.githubusercontent.com/u/15176096?v=4",
"profile": "http://sarafederi.co",
"contributions": [
"code",
"review",
"question",
"doc",
"maintenance"
]
},
{
"login": "justingreenberg",
"name": "Justin Greenberg",
"avatar_url": "https://avatars1.githubusercontent.com/u/1539088?v=4",
"profile": "https://justingreenberg.com",
"contributions": [
"code",
"review"
]
},
{
"login": "jwinn",
"name": "Jon Winn",
"avatar_url": "https://avatars3.githubusercontent.com/u/891726?v=4",
"profile": "https://github.com/jwinn",
"contributions": [
"code",
"review"
]
},
{
"login": "Mensae",
"name": "Johan Meester",
"avatar_url": "https://avatars2.githubusercontent.com/u/474743?v=4",
"profile": "https://meester-johan.info/",
"contributions": [
"code",
"test",
"doc"
]
},
{
"login": "Dattaya",
"name": "Yaroslav Kiliba",
"avatar_url": "https://avatars3.githubusercontent.com/u/387256?v=4",
"profile": "https://github.com/Dattaya",
"contributions": [
"code"
]
},
{
"login": "gihrig",
"name": "Glen Ihrig",
"avatar_url": "https://avatars2.githubusercontent.com/u/1481063?v=4",
"profile": "https://github.com/gihrig",
"contributions": [
"code"
]
},
{
"login": "somus",
"name": "Somasundaram Ayyappan",
"avatar_url": "https://avatars3.githubusercontent.com/u/1802828?v=4",
"profile": "https://github.com/somus",
"contributions": [
"code"
]
},
{
"login": "oliverturner",
"name": "Oliver Turner",
"avatar_url": "https://avatars0.githubusercontent.com/u/21795?v=4",
"profile": "https://www.codedsignal.co.uk/",
"contributions": [
"code"
]
},
{
"login": "samit4me",
"name": "Samuel Sharpe",
"avatar_url": "https://avatars3.githubusercontent.com/u/3248531?v=4",
"profile": "https://github.com/samit4me",
"contributions": [
"code"
]
},
{
"login": "KarandikarMihir",
"name": "Mihir Karandikar",
"avatar_url": "https://avatars3.githubusercontent.com/u/17466938?v=4",
"profile": "https://karandikarmihir.github.io/",
"contributions": [
"code"
]
},
{
"login": "v",
"name": "Vaibhav Verma",
"avatar_url": "https://avatars2.githubusercontent.com/u/627846?v=4",
"profile": "http://www.vverma.net",
"contributions": [
"code"
]
},
{
"login": "sedubois",
"name": "Sébastien Dubois",
"avatar_url": "https://avatars1.githubusercontent.com/u/4217871?v=4",
"profile": "https://imagineclarity.com",
"contributions": [
"code"
]
},
{
"login": "chaintng",
"name": "Chainarong Tangsurakit",
"avatar_url": "https://avatars2.githubusercontent.com/u/2979072?v=4",
"profile": "https://www.chaintng.com",
"contributions": [
"code"
]
},
{
"login": "amilajack",
"name": "Amila Welihinda",
"avatar_url": "https://avatars1.githubusercontent.com/u/6374832?v=4",
"profile": "https://amilajack.com",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 8
}
......@@ -10,10 +10,12 @@ module.exports = {
extends: ['airbnb', 'prettier', 'prettier/react'],
plugins: ['prettier', 'redux-saga', 'react', 'react-hooks', 'jsx-a11y'],
env: {
jest: true,
browser: true,
node: true,
es6: true,
mocha: true,
jest: true,
jasmine: true,
},
parserOptions: {
ecmaVersion: 6,
......@@ -25,6 +27,8 @@ module.exports = {
rules: {
'prettier/prettier': ['error', prettierOptions],
'arrow-body-style': [2, 'as-needed'],
'generator-star-spacing': 0,
'function-paren-newline': 0,
'class-methods-use-this': 0,
'import/imports-first': 0,
'import/newline-after-import': 0,
......@@ -41,21 +45,24 @@ module.exports = {
SwitchCase: 1,
},
],
'import/no-cycle': 0,
'no-use-before-define': ['error', { functions: false, classes: true, variables: true }],
'jsx-a11y/aria-props': 2,
'jsx-a11y/heading-has-content': 0,
'jsx-a11y/label-has-associated-control': [
2,
{
// NOTE: If this error triggers, either disable it or add
// your custom components, labels and attributes via these options
// See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md
controlComponents: ['Input'],
},
],
'jsx-a11y/label-has-for': 0,
'jsx-a11y/mouse-events-have-key-events': 2,
'jsx-a11y/role-has-required-aria-props': 2,
'jsx-a11y/role-supports-aria-props': 2,
// 'jsx-a11y/label-has-for': 0,
// 'jsx-a11y/mouse-events-have-key-events': 2,
// 'jsx-a11y/role-has-required-aria-props': 2,
// 'jsx-a11y/role-supports-aria-props': 2,
'jsx-a11y/no-noninteractive-element-interactions': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/anchor-is-valid': 0,
'max-len': 0,
'newline-per-chained-call': 0,
'no-confusing-arrow': 0,
......@@ -63,7 +70,16 @@ module.exports = {
'no-unused-vars': 2,
'no-use-before-define': 0,
'prefer-template': 2,
'react/jsx-wrap-multilines': 0,
'react/prop-types': 0,
'react/forbid-prop-types': 0,
'react/sort-comp': 1,
'react/jsx-one-expression-per-line': 0,
'react/jsx-props-no-spreading': 0,
'react/state-in-constructor': 0,
'react/static-property-placement': 0,
'react/destructuring-assignment': 0,
'react/no-array-index-key': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react/jsx-closing-tag-location': 0,
'react/forbid-prop-types': 0,
......@@ -75,6 +91,8 @@ module.exports = {
'react/require-extension': 0,
'react/self-closing-comp': 0,
'react/sort-comp': 0,
'react/require-default-props': 0,
'react/jsx-fragments': 0,
'redux-saga/no-yield-in-race': 2,
'redux-saga/yield-effects': 2,
'require-yield': 0,
......@@ -85,5 +103,6 @@ module.exports = {
config: './internals/webpack/webpack.prod.babel.js',
},
},
polyfills: ['fetch', 'Promise', 'URL', 'object-assign'],
},
};
---
name: Bug report
about: Create a report to help us improve
---
Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/react-boilerplate/react-boilerplate/blob/master/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved.
## Description
A clear and concise description of what the bug is.
## Steps to reproduce
Steps to reproduce the behavior:
(Add link to a demo on https://jsfiddle.net or similar if possible)
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
## Versions
- React-Boilerplate:
- Node/NPM:
- Browser:
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
## React Boilerplate
Thank you for contributing! Please take a moment to review our [**contributing guidelines**](https://github.com/react-boilerplate/react-boilerplate/blob/master/CONTRIBUTING.md)
to make the process easy and effective for everyone involved.
**Please open an issue** before embarking on any significant pull request, especially those that
add a new library or change existing tests, otherwise you risk spending a lot of time working
on something that might not end up being merged into the project.
Before opening a pull request, please ensure:
- [ ] You have followed our [**contributing guidelines**](https://github.com/react-boilerplate/react-boilerplate/blob/master/CONTRIBUTING.md)
- [ ] Double-check your branch is based on `dev` and targets `dev`
- [ ] Pull request has tests (we are going for 100% coverage!)
- [ ] Code is well-commented, linted and follows project conventions
- [ ] Documentation is updated (if necessary)
- [ ] Internal code generators and templates are updated (if necessary)
- [ ] Description explains the issue/use-case resolved and auto-closes related issues
Be kind to code reviewers, please try to keep pull requests as small and focused as possible :)
**IMPORTANT**: By submitting a patch, you agree to allow the project
owners to license your work under the terms of the [MIT License](https://github.com/react-boilerplate/react-boilerplate/blob/master/LICENSE.md).
comment: This issue was automatically closed because it does not follow either one of our templates. Please open a new issue and fill out the template that appears instead of deleting it. If you're reporting an issue, it's especially important that you provide detailed steps for how to reproduce it.
issueConfigs:
- content:
- Description
- Steps to reproduce
- Versions
- content:
- Is your feature request related to a problem
- Describe the solution you'd like
- Describe alternatives you've considered
- Additional context
# Configuration for lock-threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 30
# Issues and pull requests with these labels will not be locked. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
......@@ -8,3 +8,4 @@ stats.json
.DS_Store
npm-debug.log
.idea
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
# Contributing to react-boilerplate
Love react-boilerplate and want to help? Thanks so much, there's something to do for everybody!
Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features.
## Using the issue tracker
The [issue tracker](https://github.com/react-boilerplate/react-boilerplate/issues) is
the preferred channel for [bug reports](#bugs), [features requests](#features)
and [submitting pull requests](#pull-requests).
<a name="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; ideally create a [reduced test case](https://css-tricks.com/reduced-test-cases/) and a live example.
4. **Use the bug report template** &mdash; please fill in the template which appears when you open a new issue.
A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS
experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential bugs.
Example:
> ## Description
> A clear and concise description of what the bug is.
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
>
> ## Steps to reproduce
> Steps to reproduce the behavior:
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> (Add link to a demo on https://jsfiddle.net or similar if possible)
>
> **Expected behavior**
> A clear and concise description of what you expected to happen.
>
> **Screenshots**
> If applicable, add screenshots to help explain your problem.
>
> ## Versions
>
> - React-Boilerplate:
> - Node/NPM:
> - Browser:
<a name="features"></a>
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to _you_ to make a strong case to convince the project's developers of the merits of this feature. Please provide as many details and as much context as possible.
There is also a template for feature requests. Please make sure to use it.
<a name="pull-requests"></a>
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**Please ask first** before embarking on any significant pull request (e.g.
implementing features, refactoring code, porting to a different language),
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.
Please adhere to the coding conventions used throughout a project (indentation,
accurate comments, etc.) and any other requirements (such as test coverage).
Since the `master` branch is what people actually use in production, we have a
`dev` branch that unstable changes get merged into first. Only when we
consider that stable we merge it into the `master` branch and release the
changes for real.
Adhering to the following process is the best way to get your work
included in the project:
1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/react-boilerplate.git
# Navigate to the newly cloned directory
cd react-boilerplate
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/react-boilerplate/react-boilerplate.git
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout dev
git pull upstream dev
```
3. Create a new topic branch (off the `dev` branch) to contain your feature, change, or fix:
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/about-git-rebase/) feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream dev branch into your topic branch:
```bash
git pull [--rebase] upstream dev
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
**IMPORTANT**: By submitting a patch, you agree to allow the project
owners to license your work under the terms of the [MIT License](https://github.com/react-boilerplate/react-boilerplate/blob/master/LICENSE.md).
# Collaborating guidelines
You can find the list of all contributors in [README.md](./README.md).
There are few basic rules to ensure high quality of the boilerplate:
- Before merging, a PR requires at least two approvals from the collaborators unless it's an architectural change, a large feature, etc. If it is, then at least 50% of the core team have to agree to merge it, with every team member having a full veto right. (i.e. every single one can block any PR)
- A PR should remain open for at least two days before merging (does not apply for trivial contributions like fixing a typo). This way everyone has enough time to look into it.
You are always welcome to discuss and propose improvements to this guideline.
# Add yourself as a contributor
This project follows the [All Contributors specification](https://allcontributors.org/). To add yourself to the table of contributors in the README file, please use the [bot](https://allcontributors.org/docs/en/bot/overview) or the [CLI](https://allcontributors.org/docs/en/cli/overview) as part of your PR.
If you've already added yourself to the list and are making a new type of contribution, you can run it again and select the new contribution type.
This diff is collapsed.
/**
* A link to a certain page, an anchor tag
*/
import styled from 'styled-components';
const A = styled.a`
color: #41addd;
&:hover {
color: #6cc0e5;
}
`;
export default A;
/**
* Testing our link component
*/
import React from 'react';
import { render } from 'react-testing-library';
import A from '../index';
const href = 'http://mxstbr.com/';
const children = <h1>Test</h1>;
const renderComponent = (props = {}) =>
render(
<A href={href} {...props}>
{children}
</A>,
);
describe('<A />', () => {
it('should render an <a> tag', () => {
const { container } = renderComponent();
expect(container.querySelector('a')).not.toBeNull();
});
it('should have an href attribute', () => {
const { container } = renderComponent();
expect(container.querySelector('a').href).toEqual(href);
});
it('should have children', () => {
const { container } = renderComponent();
expect(container.querySelector('a').children).toHaveLength(1);
});
it('should have a class attribute', () => {
const className = 'test';
const { container } = renderComponent({ className });
expect(container.querySelector('a').classList).toContain(className);
});
it('should adopt a target attribute', () => {
const target = '_blank';
const { container } = renderComponent({ target });
expect(container.querySelector('a').target).toEqual(target);
});
it('should adopt a type attribute', () => {
const type = 'text/html';
const { container } = renderComponent({ type });
expect(container.querySelector('a').type).toEqual(type);
});
});
import styled from 'styled-components';
import buttonStyles from './buttonStyles';
const A = styled.a`
${buttonStyles};
`;
export default A;
import styled from 'styled-components';
import buttonStyles from './buttonStyles';
const StyledButton = styled.button`
${buttonStyles};
`;
export default StyledButton;
import styled from 'styled-components';
const Wrapper = styled.div`
width: 100%;
text-align: center;
margin: 4em 0;
`;
export default Wrapper;
import { css } from 'styled-components';
const buttonStyles = css`
display: inline-block;
box-sizing: border-box;
padding: 0.25em 2em;
text-decoration: none;
border-radius: 4px;
-webkit-font-smoothing: antialiased;
-webkit-touch-callout: none;
user-select: none;
cursor: pointer;
outline: 0;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: bold;
font-size: 16px;
border: 2px solid #41addd;
color: #41addd;
&:active {
background: #41addd;
color: #fff;
}
`;
export default buttonStyles;
/**
*
* Button.js
*
* A common button, if you pass it a prop "route" it'll render a link to a react-router route
* otherwise it'll render a link with an onclick
*/
import React, { Children } from 'react';
import PropTypes from 'prop-types';
import A from './A';
import StyledButton from './StyledButton';
import Wrapper from './Wrapper';
function Button(props) {
// Render an anchor tag
let button = (
<A href={props.href} onClick={props.onClick}>
{Children.toArray(props.children)}
</A>
);
// If the Button has a handleRoute prop, we want to render a button
if (props.handleRoute) {
button = (
<StyledButton onClick={props.handleRoute}>
{Children.toArray(props.children)}
</StyledButton>
);
}
return <Wrapper>{button}</Wrapper>;
}
Button.propTypes = {
handleRoute: PropTypes.func,
href: PropTypes.string,
onClick: PropTypes.func,
children: PropTypes.node.isRequired,
};
export default Button;
import React from 'react';
import { render } from 'react-testing-library';
import A from '../A';
describe('<A />', () => {
it('should render an <a> tag', () => {
const { container } = render(<A />);
expect(container.querySelector('a')).not.toBeNull();
});
it('should have a class attribute', () => {
const { container } = render(<A />);
expect(container.querySelector('a').hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<A id={id} />);
expect(container.querySelector('a').id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<A attribute="test" />);
expect(container.querySelector('a[attribute="test"]')).toBeNull();
});
});
import React from 'react';
import { render } from 'react-testing-library';
import StyledButton from '../StyledButton';
describe('<StyledButton />', () => {
it('should render an <button> tag', () => {
const { container } = render(<StyledButton />);
expect(container.querySelector('button')).not.toBeNull();
});
it('should have a class attribute', () => {
const { container } = render(<StyledButton />);
expect(container.querySelector('button').hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<StyledButton id={id} />);
expect(container.querySelector('button').id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<StyledButton attribute="test" />);
expect(container.querySelector('button[attribute="test"]')).toBeNull();
});
});
import React from 'react';
import { render } from 'react-testing-library';
import Wrapper from '../Wrapper';
describe('<Wrapper />', () => {
it('should render an <div> tag', () => {
const { container } = render(<Wrapper />);
expect(container.querySelector('div')).not.toBeNull();
});
it('should have a class attribute', () => {
const { container } = render(<Wrapper />);
expect(container.querySelector('div').hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Wrapper id={id} />);
expect(container.querySelector('div').id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Wrapper attribute="test" />);
expect(container.querySelector('div[attribute="test"]')).toBeNull();
});
});
/**
* Testing our Button component
*/
import React from 'react';
import { fireEvent, render } from 'react-testing-library';
import Button from '../index';
const handleRoute = () => {};
const href = 'http://mxstbr.com';
const children = <h1>Test</h1>;
const renderComponent = (props = {}) =>
render(
<Button href={href} {...props}>
{children}
</Button>,
);
describe('<Button />', () => {
it('should render an <a> tag if no route is specified', () => {
const { container } = renderComponent({ href });
expect(container.querySelector('a')).not.toBeNull();
});
it('should render a <button> tag to change route if the handleRoute prop is specified', () => {
const { container } = renderComponent({ handleRoute });
expect(container.querySelector('button')).toBeDefined();
});
it('should have children', () => {
const { container } = renderComponent();
expect(container.querySelector('a').children).toHaveLength(1);
});
it('should handle click events', () => {
const onClickSpy = jest.fn();
const { container } = renderComponent({ onClick: onClickSpy });
fireEvent.click(container.querySelector('a'));
expect(onClickSpy).toHaveBeenCalled();
});
it('should have a class attribute', () => {
const { container } = renderComponent();
expect(container.querySelector('a').hasAttribute('class')).toBe(true);
});
it('should not adopt a type attribute when rendering an <a> tag', () => {
const type = 'text/html';
const { container } = renderComponent({ href, type });
expect(container.querySelector(`a[type="${type}"]`)).toBeNull();
});
it('should not adopt a type attribute when rendering a button', () => {
const type = 'submit';
const { container } = renderComponent({ handleRoute, type });
expect(container.querySelector('button').getAttribute('type')).toBeNull();
});
});
import styled from 'styled-components';
const Wrapper = styled.footer`
display: flex;
justify-content: space-between;
padding: 3em 0;
border-top: 1px solid #666;
`;
export default Wrapper;
import React from 'react';
import { FormattedMessage } from 'react-intl';
import A from 'components/A';
import LocaleToggle from 'containers/LocaleToggle';
import Wrapper from './Wrapper';
import messages from './messages';
function Footer() {
return (
<Wrapper>
<section>
<FormattedMessage {...messages.licenseMessage} />
</section>
<section>
<LocaleToggle />
</section>
<section>
<FormattedMessage
{...messages.authorMessage}
values={{
author: <A href="https://twitter.com/mxstbr">Max Stoiber</A>,
}}
/>
</section>
</Wrapper>
);
}
export default Footer;
/*
* Footer Messages
*
* This contains all the text for the Footer component.
*/
import { defineMessages } from 'react-intl';
export const scope = 'boilerplate.components.Footer';
export default defineMessages({
licenseMessage: {
id: `${scope}.license.message`,
defaultMessage: 'This project is licensed under the MIT license.',
},
authorMessage: {
id: `${scope}.author.message`,
defaultMessage: `
Made with love by {author}.
`,
},
});
import React from 'react';
import { render } from 'react-testing-library';
import Wrapper from '../Wrapper';
describe('<Wrapper />', () => {
it('should render an <footer> tag', () => {
const { container } = render(<Wrapper />);
expect(container.querySelector('footer')).not.toBeNull();
});
it('should have a class attribute', () => {
const { container } = render(<Wrapper />);
expect(container.querySelector('footer').hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Wrapper id={id} />);
expect(container.querySelector('footer').id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Wrapper attribute="test" />);
expect(
container.querySelector('footer').getAttribute('attribute'),
).toBeNull();
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> should render and match the snapshot 1`] = `
<footer
className="Wrapper-wcfdfo-0 dPvBog"
>
<section>
<span>
This project is licensed under the MIT license.
</span>
</section>
<section>
<div
className="Wrapper-xnjoq9-0 dNmQIR"
>
<select
className="Select-sc-1sm01tk-0 wIjFa"
onChange={[Function]}
value="en"
>
<option
value="en"
>
en
</option>
<option
value="de"
>
de
</option>
</select>
</div>
</section>
<section>
<span>
Made with love by
<a
className="A-br8h0y-0 dwVNvo"
href="https://twitter.com/mxstbr"
>
Max Stoiber
</a>
.
</span>
</section>
</footer>
`;
import React from 'react';
import renderer from 'react-test-renderer';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router-dom';
import Footer from '../index';
import configureStore from '../../../configureStore';
describe('<Footer />', () => {
let store;
beforeAll(() => {
store = configureStore({}, browserHistory);
});
it('should render and match the snapshot', () => {
const renderedComponent = renderer
.create(
<Provider store={store}>
<IntlProvider locale="en">
<Footer />
</IntlProvider>
</Provider>,
)
.toJSON();
expect(renderedComponent).toMatchSnapshot();
});
});
import styled from 'styled-components';
const H1 = styled.h1`
font-size: 2em;
margin-bottom: 0.25em;
`;
export default H1;
import React from 'react';
import { render } from 'react-testing-library';
import H1 from '../index';
describe('<H1 />', () => {
it('should render a prop', () => {
const id = 'testId';
const { container } = render(<H1 id={id} />);
expect(container.querySelector('h1').id).toEqual(id);
});
it('should render its text', () => {
const children = 'Text';
const { container, queryByText } = render(<H1>{children}</H1>);
const { childNodes } = container.querySelector('h1');
expect(childNodes).toHaveLength(1);
expect(queryByText(children)).not.toBeNull();
});
});
import styled from 'styled-components';
const H2 = styled.h2`
font-size: 1.5em;
`;
export default H2;
import React from 'react';
import { render } from 'react-testing-library';
import H2 from '../index';
describe('<H2 />', () => {
it('should render a prop', () => {
const id = 'testId';
const { container } = render(<H2 id={id} />);
expect(container.querySelector('h2').id).toEqual(id);
});
it('should render its text', () => {
const children = 'Text';
const { container, queryByText } = render(<H2>{children}</H2>);
const { childNodes } = container.querySelector('h2');
expect(childNodes).toHaveLength(1);
expect(queryByText(children)).not.toBeNull();
});
});
import React from 'react';
function H3(props) {
return <h3 {...props} />;
}
export default H3;
import React from 'react';
import { render } from 'react-testing-library';
import H3 from '../index';
describe('<H3 />', () => {
it('should render a prop', () => {
const id = 'testId';
const { container } = render(<H3 id={id} />);
expect(container.querySelector('h3').id).toEqual(id);
});
it('should render its text', () => {
const children = 'Text';
const { container, queryByText } = render(<H3>{children}</H3>);
const { childNodes } = container.querySelector('h3');
expect(childNodes).toHaveLength(1);
expect(queryByText(children)).not.toBeNull();
});
});
import styled from 'styled-components';
import NormalA from 'components/A';
const A = styled(NormalA)`
padding: 2em 0;
`;
export default A;
import { Link } from 'react-router-dom';
import styled from 'styled-components';
export default styled(Link)`
display: inline-flex;
padding: 0.25em 2em;
margin: 1em;
text-decoration: none;
border-radius: 4px;
-webkit-font-smoothing: antialiased;
-webkit-touch-callout: none;
user-select: none;
cursor: pointer;
outline: 0;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: bold;
font-size: 16px;
border: 2px solid #41addd;
color: #41addd;
&:active {
background: #41addd;
color: #fff;
}
`;
import styled from 'styled-components';
import NormalImg from 'components/Img';
const Img = styled(NormalImg)`
width: 100%;
margin: 0 auto;
display: block;
`;
export default Img;
import styled from 'styled-components';
export default styled.div`
text-align: center;
`;
This diff was suppressed by a .gitattributes entry.
import React from 'react';
import { FormattedMessage } from 'react-intl';
import A from './A';
import Img from './Img';
import NavBar from './NavBar';
import HeaderLink from './HeaderLink';
import Banner from './banner.jpg';
import messages from './messages';
function Header() {
return (
<div>
<A href="https://www.reactboilerplate.com/">
<Img src={Banner} alt="react-boilerplate - Logo" />
</A>
<NavBar>
<HeaderLink to="/">
<FormattedMessage {...messages.home} />
</HeaderLink>
<HeaderLink to="/features">
<FormattedMessage {...messages.features} />
</HeaderLink>
</NavBar>
</div>
);
}
export default Header;
/*
* HomePage Messages
*
* This contains all the text for the HomePage component.
*/
import { defineMessages } from 'react-intl';
export const scope = 'boilerplate.components.Header';
export default defineMessages({
home: {
id: `${scope}.home`,
defaultMessage: 'Home',
},
features: {
id: `${scope}.features`,
defaultMessage: 'Features',
},
});
import React from 'react';
import { render } from 'react-testing-library';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import A from '../A';
describe('<A />', () => {
it('should match the snapshot', () => {
const renderedComponent = renderer.create(<A />).toJSON();
expect(renderedComponent).toMatchSnapshot();
});
it('should have a class attribute', () => {
const { container } = render(<A />);
expect(container.querySelector('a').hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<A id={id} />);
expect(container.querySelector('a').id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<A attribute="test" />);
expect(container.querySelector('a').getAttribute('attribute')).toBeNull();
});
});
import React from 'react';
import { render } from 'react-testing-library';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import Img from '../Img';
describe('<Img />', () => {
it('should match the snapshot', () => {
const renderedComponent = renderer
.create(<Img src="http://example.com/test.jpg" alt="test" />)
.toJSON();
expect(renderedComponent).toMatchSnapshot();
});
it('should have a class attribute', () => {
const { container } = render(
<Img src="http://example.com/test.jpg" alt="test" />,
);
expect(container.querySelector('img').hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const { container } = render(
<Img src="http://example.com/test.jpg" alt="test" />,
);
expect(container.querySelector('img').alt).toEqual('test');
});
it('should not adopt an invalid attribute', () => {
const { container } = render(
<Img src="http://example.com/test.jpg" attribute="test" alt="test" />,
);
expect(container.querySelector('img').getAttribute('attribute')).toBeNull();
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<A /> should match the snapshot 1`] = `
.c0 {
color: #41addd;
padding: 2em 0;
}
.c0:hover {
color: #6cc0e5;
}
<a
className="c0"
/>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Img /> should match the snapshot 1`] = `
.c0 {
width: 100%;
margin: 0 auto;
display: block;
}
<img
alt="test"
className="c0"
src="http://example.com/test.jpg"
/>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> should render a div 1`] = `
<div>
<a
class="A-br8h0y-0 A-p44m4v-0 cYguYL"
href="https://www.reactboilerplate.com/"
>
<img
alt="react-boilerplate - Logo"
class="Img-sc-9pa8on-0 eepIiv"
src="IMAGE_MOCK"
/>
</a>
<div
class="NavBar-sc-3b48xb-0 ioQxlc"
>
<a
class="HeaderLink-sc-1mtyaiv-0 hMiTYG"
href="/"
>
<span>
Home
</span>
</a>
<a
class="HeaderLink-sc-1mtyaiv-0 hMiTYG"
href="/features"
>
<span>
Features
</span>
</a>
</div>
</div>
`;
import React from 'react';
import { render } from 'react-testing-library';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
import { ConnectedRouter } from 'connected-react-router/immutable';
import { createMemoryHistory } from 'history';
import Header from '../index';
import configureStore from '../../../configureStore';
describe('<Header />', () => {
const history = createMemoryHistory();
const store = configureStore({}, history);
it('should render a div', () => {
const { container } = render(
<Provider store={store}>
<IntlProvider locale="en">
<ConnectedRouter history={history}>
<Header />
</ConnectedRouter>
</IntlProvider>
</Provider>,
);
expect(container.firstChild).toMatchSnapshot();
});
});
/**
*
* Img.js
*
* Renders an image, enforcing the usage of the alt="" tag
*/
import React from 'react';
import PropTypes from 'prop-types';
function Img(props) {
return <img className={props.className} src={props.src} alt={props.alt} />;
}
// We require the use of src and alt, only enforced by react in dev mode
Img.propTypes = {
src: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
alt: PropTypes.string.isRequired,
className: PropTypes.string,
};
export default Img;
import React from 'react';
import { render } from 'react-testing-library';
import Img from '../index';
const src = 'test.png';
const alt = 'test';
const renderComponent = (props = {}) =>
render(<Img src={src} alt={alt} {...props} />);
describe('<Img />', () => {
it('should render an <img> tag', () => {
const { container } = renderComponent();
const element = container.querySelector('img');
expect(element).not.toBeNull();
});
it('should have an src attribute', () => {
const { container } = renderComponent();
const element = container.querySelector('img');
expect(element.hasAttribute('src')).toBe(true);
});
it('should have an alt attribute', () => {
const { container } = renderComponent();
const element = container.querySelector('img');
expect(element.hasAttribute('alt')).toBe(true);
});
it('should not have a class attribute', () => {
const { container } = renderComponent();
const element = container.querySelector('img');
expect(element.hasAttribute('class')).toBe(false);
});
it('should adopt a className attribute', () => {
const className = 'test';
const { container } = renderComponent({ className });
const element = container.querySelector('img');
expect(element.className).toEqual(className);
});
it('should not adopt a srcset attribute', () => {
const srcset = 'test-HD.png 2x';
const { container } = renderComponent({ srcset });
const element = container.querySelector('img');
expect(element.hasAttribute('srcset')).toBe(false);
});
});
import React from 'react';
import PropTypes from 'prop-types';
function IssueIcon(props) {
return (
<svg height="1em" width="0.875em" className={props.className} {...props}>
<path d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m1 3H6v5h2V4z m0 6H6v2h2V10z" />
</svg>
);
}
IssueIcon.propTypes = {
className: PropTypes.string,
};
export default IssueIcon;
import React from 'react';
import { render } from 'react-testing-library';
import IssueIcon from '../index';
describe('<IssueIcon />', () => {
it('should render a SVG', () => {
const { container } = render(<IssueIcon />);
expect(container.querySelector('svg')).not.toBeNull();
});
});
import styled from 'styled-components';
const Ul = styled.ul`
list-style: none;
margin: 0;
width: 100%;
max-height: 30em;
overflow-y: auto;
padding: 0 1em;
`;
export default Ul;
import styled from 'styled-components';
const Wrapper = styled.div`
padding: 0;
margin: 0;
width: 100%;
background-color: white;
border: 1px solid #ccc;
border-radius: 3px;
overflow: hidden;
`;
export default Wrapper;
import React from 'react';
import PropTypes from 'prop-types';
import Ul from './Ul';
import Wrapper from './Wrapper';
function List(props) {
const ComponentToRender = props.component;
let content = <div />;
// If we have items, render them
if (props.items) {
content = props.items.map(item => (
<ComponentToRender key={`item-${item.id}`} item={item} />
));
} else {
// Otherwise render a single component
content = <ComponentToRender />;
}
return (
<Wrapper>
<Ul>{content}</Ul>
</Wrapper>
);
}
List.propTypes = {
component: PropTypes.elementType.isRequired,
items: PropTypes.array,
};
export default List;
import React from 'react';
import { render } from 'react-testing-library';
import Ul from '../Ul';
describe('<Ul />', () => {
it('should render an <ul> tag', () => {
const { container } = render(<Ul />);
const element = container.firstElementChild;
expect(element.tagName).toEqual('UL');
});
it('should have a class attribute', () => {
const { container } = render(<Ul />);
const element = container.firstElementChild;
expect(element.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Ul id={id} />);
const element = container.firstElementChild;
expect(element.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Ul attribute="test" />);
const element = container.firstElementChild;
expect(element.hasAttribute('attribute')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import Wrapper from '../Wrapper';
describe('<Wrapper />', () => {
it('should render an <div> tag', () => {
const { container } = render(<Wrapper />);
expect(container.firstElementChild.tagName).toEqual('DIV');
});
it('should have a class attribute', () => {
const { container } = render(<Wrapper />);
const element = container.firstElementChild;
expect(element.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Wrapper id={id} />);
const element = container.firstElementChild;
expect(element.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Wrapper attribute="test" />);
const element = container.firstElementChild;
expect(element.hasAttribute('attribute')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import List from '../index';
describe('<List />', () => {
it('should render the passed component if no items are passed', () => {
const component = () => <li>test</li>; // eslint-disable-line react/prop-types
const { container } = render(<List component={component} />);
expect(container.querySelector('li')).not.toBeNull();
});
it('should pass all items props to rendered component', () => {
const items = [{ id: 1, name: 'Hello' }, { id: 2, name: 'World' }];
const component = ({ item }) => <li>{item.name}</li>; // eslint-disable-line react/prop-types
const { container, getByText } = render(
<List items={items} component={component} />,
);
const elements = container.querySelectorAll('li');
expect(elements).toHaveLength(2);
expect(getByText(items[0].name)).not.toBeNull();
expect(getByText(items[1].name)).not.toBeNull();
});
});
import styled from 'styled-components';
const Item = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
align-items: center;
`;
export default Item;
import styled from 'styled-components';
const Wrapper = styled.li`
width: 100%;
height: 3em;
display: flex;
align-items: center;
position: relative;
border-top: 1px solid #eee;
&:first-child {
border-top: none;
}
`;
export default Wrapper;
import React from 'react';
import PropTypes from 'prop-types';
import Item from './Item';
import Wrapper from './Wrapper';
function ListItem(props) {
return (
<Wrapper>
<Item>{props.item}</Item>
</Wrapper>
);
}
ListItem.propTypes = {
item: PropTypes.any,
};
export default ListItem;
import React from 'react';
import { render } from 'react-testing-library';
import Item from '../Item';
describe('<Item />', () => {
it('should render an <div> tag', () => {
const { container } = render(<Item />);
expect(container.firstChild.tagName).toEqual('DIV');
});
it('should have a class attribute', () => {
const { container } = render(<Item />);
expect(container.firstChild.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Item id={id} />);
expect(container.firstChild.hasAttribute('id')).toBe(true);
expect(container.firstChild.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Item attribute="test" />);
expect(container.firstChild.hasAttribute('attribute')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import Wrapper from '../Wrapper';
describe('<Wrapper />', () => {
it('should render an <li> tag', () => {
const { container } = render(<Wrapper />);
const element = container.querySelector('li');
expect(element).not.toBeNull();
});
it('should have a class attribute', () => {
const { container } = render(<Wrapper />);
const element = container.querySelector('li');
expect(element.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Wrapper id={id} />);
const element = container.querySelector('li');
expect(element.hasAttribute('id')).toBe(true);
expect(element.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Wrapper attribute="test" />);
const element = container.querySelector('li');
expect(element.hasAttribute('attribute')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import 'jest-dom/extend-expect';
import ListItem from '../index';
describe('<ListItem />', () => {
it('should have a class', () => {
const { container } = render(<ListItem className="test" />);
expect(container.querySelector('li').hasAttribute('class')).toBe(true);
});
it('should render the content passed to it', () => {
const content = <div data-testid="test">Hello world!</div>;
const { getByTestId } = render(<ListItem item={content} />);
expect(getByTestId('test').tagName).toEqual('DIV');
expect(getByTestId('test')).toHaveTextContent('Hello world!');
});
});
import React from 'react';
import PropTypes from 'prop-types';
import styled, { keyframes } from 'styled-components';
const circleFadeDelay = keyframes`
0%,
39%,
100% {
opacity: 0;
}
40% {
opacity: 1;
}
`;
const Circle = props => {
const CirclePrimitive = styled.div`
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
${props.rotate &&
`
-webkit-transform: rotate(${props.rotate}deg);
-ms-transform: rotate(${props.rotate}deg);
transform: rotate(${props.rotate}deg);
`} &:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
animation: ${circleFadeDelay} 1.2s infinite ease-in-out both;
${props.delay &&
`
-webkit-animation-delay: ${props.delay}s;
animation-delay: ${props.delay}s;
`};
}
`;
return <CirclePrimitive />;
};
Circle.propTypes = {
delay: PropTypes.number,
rotate: PropTypes.number,
};
export default Circle;
import styled from 'styled-components';
const Wrapper = styled.div`
margin: 2em auto;
width: 40px;
height: 40px;
position: relative;
`;
export default Wrapper;
import React from 'react';
import Circle from './Circle';
import Wrapper from './Wrapper';
const LoadingIndicator = () => (
<Wrapper>
<Circle />
<Circle rotate={30} delay={-1.1} />
<Circle rotate={60} delay={-1} />
<Circle rotate={90} delay={-0.9} />
<Circle rotate={120} delay={-0.8} />
<Circle rotate={150} delay={-0.7} />
<Circle rotate={180} delay={-0.6} />
<Circle rotate={210} delay={-0.5} />
<Circle rotate={240} delay={-0.4} />
<Circle rotate={270} delay={-0.3} />
<Circle rotate={300} delay={-0.2} />
<Circle rotate={330} delay={-0.1} />
</Wrapper>
);
export default LoadingIndicator;
import React from 'react';
import { render } from 'react-testing-library';
import Circle from '../Circle';
describe('<Circle />', () => {
it('should render an <div> tag', () => {
const { container } = render(<Circle />);
expect(container.firstChild.tagName).toEqual('DIV');
});
it('should have a class attribute', () => {
const { container } = render(<Circle />);
expect(container.firstChild.hasAttribute('class')).toBe(true);
});
it('should not adopt attributes', () => {
const id = 'test';
const { container } = render(<Circle id={id} />);
expect(container.firstChild.hasAttribute('id')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import Wrapper from '../Wrapper';
describe('<Wrapper />', () => {
it('should render an <div> tag', () => {
const { container } = render(<Wrapper />);
expect(container.firstElementChild.tagName).toEqual('DIV');
});
it('should have a class attribute', () => {
const { container } = render(<Wrapper />);
const element = container.firstElementChild;
expect(element.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Wrapper id={id} />);
const element = container.firstElementChild;
expect(element.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Wrapper attribute="test" />);
const element = container.firstElementChild;
expect(element.hasAttribute('attribute')).toBe(false);
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LoadingIndicator /> should match the snapshot 1`] = `
.c0 {
margin: 2em auto;
width: 40px;
height: 40px;
position: relative;
}
.c1 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.c1:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
}
.c2 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}
.c2:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -1.1s;
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.c3 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(60deg);
-ms-transform: rotate(60deg);
-webkit-transform: rotate(60deg);
-ms-transform: rotate(60deg);
transform: rotate(60deg);
}
.c3:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -1s;
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
.c4 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.c4:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.9s;
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.c5 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(120deg);
-ms-transform: rotate(120deg);
-webkit-transform: rotate(120deg);
-ms-transform: rotate(120deg);
transform: rotate(120deg);
}
.c5:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.8s;
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.c6 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(150deg);
-ms-transform: rotate(150deg);
-webkit-transform: rotate(150deg);
-ms-transform: rotate(150deg);
transform: rotate(150deg);
}
.c6:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.7s;
-webkit-animation-delay: -0.7s;
animation-delay: -0.7s;
}
.c7 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.c7:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.6s;
-webkit-animation-delay: -0.6s;
animation-delay: -0.6s;
}
.c8 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(210deg);
-ms-transform: rotate(210deg);
-webkit-transform: rotate(210deg);
-ms-transform: rotate(210deg);
transform: rotate(210deg);
}
.c8:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.5s;
-webkit-animation-delay: -0.5s;
animation-delay: -0.5s;
}
.c9 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(240deg);
-ms-transform: rotate(240deg);
-webkit-transform: rotate(240deg);
-ms-transform: rotate(240deg);
transform: rotate(240deg);
}
.c9:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.4s;
-webkit-animation-delay: -0.4s;
animation-delay: -0.4s;
}
.c10 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.c10:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.3s;
-webkit-animation-delay: -0.3s;
animation-delay: -0.3s;
}
.c11 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(300deg);
-ms-transform: rotate(300deg);
-webkit-transform: rotate(300deg);
-ms-transform: rotate(300deg);
transform: rotate(300deg);
}
.c11:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.2s;
-webkit-animation-delay: -0.2s;
animation-delay: -0.2s;
}
.c12 {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
-webkit-transform: rotate(330deg);
-ms-transform: rotate(330deg);
-webkit-transform: rotate(330deg);
-ms-transform: rotate(330deg);
transform: rotate(330deg);
}
.c12:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #999;
border-radius: 100%;
-webkit-animation: evVlQQ 1.2s infinite ease-in-out both;
animation: evVlQQ 1.2s infinite ease-in-out both;
-webkit-animation-delay: -0.1s;
-webkit-animation-delay: -0.1s;
animation-delay: -0.1s;
}
<div
className="c0"
>
<div
className="c1"
/>
<div
className="c2"
/>
<div
className="c3"
/>
<div
className="c4"
/>
<div
className="c5"
/>
<div
className="c6"
/>
<div
className="c7"
/>
<div
className="c8"
/>
<div
className="c9"
/>
<div
className="c10"
/>
<div
className="c11"
/>
<div
className="c12"
/>
</div>
`;
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import LoadingIndicator from '../index';
describe('<LoadingIndicator />', () => {
it('should match the snapshot', () => {
const renderedComponent = renderer.create(<LoadingIndicator />).toJSON();
expect(renderedComponent).toMatchSnapshot();
});
});
import React from 'react';
import PropTypes from 'prop-types';
import List from 'components/List';
import ListItem from 'components/ListItem';
import LoadingIndicator from 'components/LoadingIndicator';
import RepoListItem from 'containers/RepoListItem';
function ReposList({ loading, error, repos }) {
if (loading) {
return <List component={LoadingIndicator} />;
}
if (error !== false) {
const ErrorComponent = () => (
<ListItem item="Something went wrong, please try again!" />
);
return <List component={ErrorComponent} />;
}
if (repos !== false) {
return <List items={repos} component={RepoListItem} />;
}
return null;
}
ReposList.propTypes = {
loading: PropTypes.bool,
error: PropTypes.any,
repos: PropTypes.any,
};
export default ReposList;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ReposList /> should render the loading indicator when its loading 1`] = `
<div
class="Wrapper-sc-1umgotm-0 laCwDT"
>
<ul
class="Ul-mwnq6h-0 jcxXFy"
>
<div
class="Wrapper-sc-12uw37d-0 bTWIVF"
>
<div
class="Circle__CirclePrimitive-ww56dy-0 enQIMS"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 jCxmwS"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 dyFXB"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 ikwUeP"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 hwlQRz"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 jjXxyb"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 jVExkJ"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 haCVIz"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 ghhiLZ"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 bCbqdt"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 hZPQMA"
/>
<div
class="Circle__CirclePrimitive-ww56dy-0 fsGOmP"
/>
</div>
</ul>
</div>
`;
exports[`<ReposList /> should render the repositories if loading was successful 1`] = `
<div
class="Wrapper-sc-1umgotm-0 laCwDT"
>
<ul
class="Ul-mwnq6h-0 jcxXFy"
>
<li
class="Wrapper-euo0oy-0 jLvxtc"
>
<div
class="Item-sc-3y9mie-0 fKXSkT"
>
<div
class="Wrapper-sc-17s0rao-0 dlZQhZ"
>
<a
class="A-br8h0y-0 RepoLink-pvpwpn-0 eEveIZ"
href="https://github.com/react-boilerplate/react-boilerplate"
target="_blank"
>
react-boilerplate
</a>
<a
class="A-br8h0y-0 IssueLink-uyzonb-0 kUUUnc"
href="https://github.com/react-boilerplate/react-boilerplate/issues"
target="_blank"
>
<svg
class="IssueIcon-s8m34y-0 jEBeEF"
height="1em"
width="0.875em"
>
<path
d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7S10.14 13.7 7 13.7 1.3 11.14 1.3 8s2.56-5.7 5.7-5.7m0-1.3C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7S10.86 1 7 1z m1 3H6v5h2V4z m0 6H6v2h2V10z"
/>
</svg>
<span>
20
</span>
</a>
</div>
</div>
</li>
</ul>
</div>
`;
import React from 'react';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router-dom';
import { render } from 'react-testing-library';
import ReposList from '../index';
import configureStore from '../../../configureStore';
describe('<ReposList />', () => {
it('should render the loading indicator when its loading', () => {
const { container } = render(<ReposList loading />);
expect(container.firstChild).toMatchSnapshot();
});
it('should render an error if loading failed', () => {
const { queryByText } = render(
<IntlProvider locale="en">
<ReposList loading={false} error={{ message: 'Loading failed!' }} />
</IntlProvider>,
);
expect(queryByText(/Something went wrong/)).not.toBeNull();
});
it('should render the repositories if loading was successful', () => {
const store = configureStore(
{ global: { currentUser: 'mxstbr' } },
browserHistory,
);
const repos = [
{
owner: {
login: 'mxstbr',
},
html_url: 'https://github.com/react-boilerplate/react-boilerplate',
name: 'react-boilerplate',
open_issues_count: 20,
full_name: 'react-boilerplate/react-boilerplate',
},
];
const { container } = render(
<Provider store={store}>
<IntlProvider locale="en">
<ReposList repos={repos} error={false} />
</IntlProvider>
</Provider>,
);
expect(container.firstChild).toMatchSnapshot();
});
it('should not render anything if nothing interesting is provided', () => {
const { container } = render(
<ReposList repos={false} error={false} loading={false} />,
);
expect(container.firstChild).toBeNull();
});
});
import styled from 'styled-components';
const Select = styled.select`
line-height: 1em;
background-color: transparent;
border-style: none;
`;
export default Select;
/**
*
* LocaleToggle
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import Select from './Select';
import ToggleOption from '../ToggleOption';
function Toggle(props) {
let content = <option>--</option>;
// If we have items, render them
if (props.values) {
content = props.values.map(value => (
<ToggleOption key={value} value={value} message={props.messages[value]} />
));
}
return (
<Select value={props.value} onChange={props.onToggle}>
{content}
</Select>
);
}
Toggle.propTypes = {
onToggle: PropTypes.func,
values: PropTypes.array,
value: PropTypes.string,
messages: PropTypes.object,
};
export default Toggle;
import React from 'react';
import { render } from 'react-testing-library';
import Select from '../Select';
describe('<Select />', () => {
it('should render an <select> tag', () => {
const { container } = render(<Select />);
expect(container.firstChild.tagName).toEqual('SELECT');
});
it('should have a class attribute', () => {
const { container } = render(<Select />);
expect(container.firstChild.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const { container } = render(<Select id={id} />);
expect(container.firstChild.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const { container } = render(<Select attribute="test" />);
expect(container.firstChild.hasAttribute('attribute')).toBe(false);
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Toggle /> should contain default text 1`] = `
<select
class="Select-sc-1sm01tk-0 wIjFa"
>
<option
value="en"
>
someContent
</option>
<option
value="de"
>
someOtherContent
</option>
</select>
`;
import React from 'react';
import { render } from 'react-testing-library';
import { IntlProvider, defineMessages } from 'react-intl';
import Toggle from '../index';
describe('<Toggle />', () => {
it('should contain default text', () => {
const defaultEnMessage = 'someContent';
const defaultDeMessage = 'someOtherContent';
const messages = defineMessages({
en: {
id: 'boilerplate.containers.LocaleToggle.en',
defaultMessage: defaultEnMessage,
},
de: {
id: 'boilerplate.containers.LocaleToggle.en',
defaultMessage: defaultDeMessage,
},
});
const { container } = render(
<IntlProvider locale="en">
<Toggle values={['en', 'de']} messages={messages} />
</IntlProvider>,
);
expect(container.firstChild).toMatchSnapshot();
});
it('should not have ToggleOptions if props.values is not defined', () => {
const { container } = render(<Toggle />);
const elements = container.querySelectorAll('option');
expect(elements).toHaveLength(1);
expect(container.firstChild.value).toEqual('--');
});
});
/**
*
* ToggleOption
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from 'react-intl';
const ToggleOption = ({ value, message, intl }) => (
<option value={value}>{message ? intl.formatMessage(message) : value}</option>
);
ToggleOption.propTypes = {
value: PropTypes.string.isRequired,
message: PropTypes.object,
intl: intlShape.isRequired,
};
export default injectIntl(ToggleOption);
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ToggleOption /> should render default language messages 1`] = `
<option
value="en"
>
someContent
</option>
`;
import React from 'react';
import { render } from 'react-testing-library';
import { IntlProvider, defineMessages } from 'react-intl';
import ToggleOption from '../index';
describe('<ToggleOption />', () => {
it('should render default language messages', () => {
const defaultEnMessage = 'someContent';
const message = defineMessages({
enMessage: {
id: 'boilerplate.containers.LocaleToggle.en',
defaultMessage: defaultEnMessage,
},
});
const { container } = render(
<IntlProvider locale="en">
<ToggleOption value="en" message={message.enMessage} />
</IntlProvider>,
);
expect(container.firstChild).toMatchSnapshot();
});
it('should display `value`(two letter language code) when `message` is absent', () => {
const { queryByText } = render(
<IntlProvider locale="de">
<ToggleOption value="de" />
</IntlProvider>,
);
expect(queryByText('de')).toBeDefined();
});
});
/**
*
* App
*
* This component is the skeleton around the actual pages, and should only
* contain code that should be seen on all pages. (e.g. navigation bar)
*/
import React from 'react';
import { Helmet } from 'react-helmet';
import styled from 'styled-components';
import { Switch, Route } from 'react-router-dom';
import HomePage from 'containers/HomePage/Loadable';
import FeaturePage from 'containers/FeaturePage/Loadable';
import NotFoundPage from 'containers/NotFoundPage/Loadable';
import Header from 'components/Header';
import Footer from 'components/Footer';
import GlobalStyle from '../../global-styles';
const AppWrapper = styled.div`
max-width: calc(768px + 16px * 2);
margin: 0 auto;
display: flex;
min-height: 100%;
padding: 0 16px;
flex-direction: column;
`;
export default function App() {
return (
<AppWrapper>
<Helmet
titleTemplate="%s - React.js Boilerplate"
defaultTitle="React.js Boilerplate"
>
<meta name="description" content="A React.js Boilerplate application" />
</Helmet>
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/features" component={FeaturePage} />
<Route path="" component={NotFoundPage} />
</Switch>
<Footer />
<GlobalStyle />
</AppWrapper>
);
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<App /> should render and match the snapshot 1`] = `
<ForwardRef>
<HelmetWrapper
defaultTitle="React.js Boilerplate"
defer={true}
encodeSpecialCharacters={true}
titleTemplate="%s - React.js Boilerplate"
>
<meta
content="A React.js Boilerplate application"
name="description"
/>
</HelmetWrapper>
<Header />
<Switch>
<Route
component={[Function]}
exact={true}
path="/"
/>
<Route
component={[Function]}
path="/features"
/>
<Route
component={[Function]}
path=""
/>
</Switch>
<Footer />
<GlobalStyleComponent />
</ForwardRef>
`;
import { LOAD_REPOS, LOAD_REPOS_SUCCESS, LOAD_REPOS_ERROR } from '../constants';
import { loadRepos, reposLoaded, repoLoadingError } from '../actions';
describe('App Actions', () => {
describe('loadRepos', () => {
it('should return the correct type', () => {
const expectedResult = {
type: LOAD_REPOS,
};
expect(loadRepos()).toEqual(expectedResult);
});
});
describe('reposLoaded', () => {
it('should return the correct type and the passed repos', () => {
const fixture = ['Test'];
const username = 'test';
const expectedResult = {
type: LOAD_REPOS_SUCCESS,
repos: fixture,
username,
};
expect(reposLoaded(fixture, username)).toEqual(expectedResult);
});
});
describe('repoLoadingError', () => {
it('should return the correct type and the error', () => {
const fixture = {
msg: 'Something went wrong!',
};
const expectedResult = {
type: LOAD_REPOS_ERROR,
error: fixture,
};
expect(repoLoadingError(fixture)).toEqual(expectedResult);
});
});
});
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import App from '../index';
const renderer = new ShallowRenderer();
describe('<App />', () => {
it('should render and match the snapshot', () => {
renderer.render(<App />);
const renderedOutput = renderer.getRenderOutput();
expect(renderedOutput).toMatchSnapshot();
});
});
import produce from 'immer';
import appReducer from '../reducer';
import { loadRepos, reposLoaded, repoLoadingError } from '../actions';
/* eslint-disable default-case, no-param-reassign */
describe('appReducer', () => {
let state;
beforeEach(() => {
state = {
loading: false,
error: false,
currentUser: false,
userData: {
repositories: false,
},
};
});
it('should return the initial state', () => {
const expectedResult = state;
expect(appReducer(undefined, {})).toEqual(expectedResult);
});
it('should handle the loadRepos action correctly', () => {
const expectedResult = produce(state, draft => {
draft.loading = true;
draft.error = false;
draft.userData.repositories = false;
});
expect(appReducer(state, loadRepos())).toEqual(expectedResult);
});
it('should handle the reposLoaded action correctly', () => {
const fixture = [
{
name: 'My Repo',
},
];
const username = 'test';
const expectedResult = produce(state, draft => {
draft.userData.repositories = fixture;
draft.loading = false;
draft.currentUser = username;
});
expect(appReducer(state, reposLoaded(fixture, username))).toEqual(
expectedResult,
);
});
it('should handle the repoLoadingError action correctly', () => {
const fixture = {
msg: 'Not found',
};
const expectedResult = produce(state, draft => {
draft.error = fixture;
draft.loading = false;
});
expect(appReducer(state, repoLoadingError(fixture))).toEqual(
expectedResult,
);
});
});
import {
selectGlobal,
makeSelectCurrentUser,
makeSelectLoading,
makeSelectError,
makeSelectRepos,
makeSelectLocation,
} from '../selectors';
describe('selectGlobal', () => {
it('should select the global state', () => {
const globalState = {};
const mockedState = {
global: globalState,
};
expect(selectGlobal(mockedState)).toEqual(globalState);
});
});
describe('makeSelectCurrentUser', () => {
const currentUserSelector = makeSelectCurrentUser();
it('should select the current user', () => {
const username = 'mxstbr';
const mockedState = {
global: {
currentUser: username,
},
};
expect(currentUserSelector(mockedState)).toEqual(username);
});
});
describe('makeSelectLoading', () => {
const loadingSelector = makeSelectLoading();
it('should select the loading', () => {
const loading = false;
const mockedState = {
global: {
loading,
},
};
expect(loadingSelector(mockedState)).toEqual(loading);
});
});
describe('makeSelectError', () => {
const errorSelector = makeSelectError();
it('should select the error', () => {
const error = 404;
const mockedState = {
global: {
error,
},
};
expect(errorSelector(mockedState)).toEqual(error);
});
});
describe('makeSelectRepos', () => {
const reposSelector = makeSelectRepos();
it('should select the repos', () => {
const repositories = [];
const mockedState = {
global: {
userData: {
repositories,
},
},
};
expect(reposSelector(mockedState)).toEqual(repositories);
});
});
describe('makeSelectLocation', () => {
const locationStateSelector = makeSelectLocation();
it('should select the location', () => {
const router = {
location: { pathname: '/foo' },
};
const mockedState = {
router,
};
expect(locationStateSelector(mockedState)).toEqual(router.location);
});
});
import styled from 'styled-components';
const List = styled.ul`
font-family: Georgia, Times, 'Times New Roman', serif;
padding-left: 1.75em;
`;
export default List;
import styled from 'styled-components';
const ListItem = styled.li`
margin: 1em 0;
`;
export default ListItem;
import styled from 'styled-components';
const ListItemTitle = styled.p`
font-weight: bold;
`;
export default ListItemTitle;
/**
* Asynchronously loads the component for FeaturePage
*/
import React from 'react';
import loadable from 'utils/loadable';
import LoadingIndicator from 'components/LoadingIndicator';
export default loadable(() => import('./index'), {
fallback: <LoadingIndicator />,
});
/*
* FeaturePage
*
* List all the features
*/
import React from 'react';
import { Helmet } from 'react-helmet';
import { FormattedMessage } from 'react-intl';
import H1 from 'components/H1';
import messages from './messages';
import List from './List';
import ListItem from './ListItem';
import ListItemTitle from './ListItemTitle';
export default function FeaturePage() {
return (
<div>
<Helmet>
<title>Feature Page</title>
<meta
name="description"
content="Feature page of React.js Boilerplate application"
/>
</Helmet>
<H1>
<FormattedMessage {...messages.header} />
</H1>
<List>
<ListItem>
<ListItemTitle>
<FormattedMessage {...messages.scaffoldingHeader} />
</ListItemTitle>
<p>
<FormattedMessage {...messages.scaffoldingMessage} />
</p>
</ListItem>
<ListItem>
<ListItemTitle>
<FormattedMessage {...messages.feedbackHeader} />
</ListItemTitle>
<p>
<FormattedMessage {...messages.feedbackMessage} />
</p>
</ListItem>
<ListItem>
<ListItemTitle>
<FormattedMessage {...messages.routingHeader} />
</ListItemTitle>
<p>
<FormattedMessage {...messages.routingMessage} />
</p>
</ListItem>
<ListItem>
<ListItemTitle>
<FormattedMessage {...messages.networkHeader} />
</ListItemTitle>
<p>
<FormattedMessage {...messages.networkMessage} />
</p>
</ListItem>
<ListItem>
<ListItemTitle>
<FormattedMessage {...messages.intlHeader} />
</ListItemTitle>
<p>
<FormattedMessage {...messages.intlMessage} />
</p>
</ListItem>
</List>
</div>
);
}
/*
* FeaturePage Messages
*
* This contains all the text for the FeaturePage component.
*/
import { defineMessages } from 'react-intl';
export const scope = 'boilerplate.containers.FeaturePage';
export default defineMessages({
header: {
id: `${scope}.header`,
defaultMessage: 'Features',
},
scaffoldingHeader: {
id: `${scope}.scaffolding.header`,
defaultMessage: 'Quick scaffolding',
},
scaffoldingMessage: {
id: `${scope}.scaffolding.message`,
defaultMessage: `Automate the creation of components, containers, routes, selectors
and sagas - and their tests - right from the CLI!`,
},
feedbackHeader: {
id: `${scope}.feedback.header`,
defaultMessage: 'Instant feedback',
},
feedbackMessage: {
id: `${scope}.feedback.message`,
defaultMessage: `
Enjoy the best DX and code your app at the speed of thought! Your
saved changes to the CSS and JS are reflected instantaneously
without refreshing the page. Preserve application state even when
you update something in the underlying code!
`,
},
stateManagementHeader: {
id: `${scope}.state_management.header`,
defaultMessage: 'Predictable state management',
},
stateManagementMessages: {
id: `${scope}.state_management.message`,
defaultMessage: `
Unidirectional data flow allows for change logging and time travel
debugging.
`,
},
javascriptHeader: {
id: `${scope}.javascript.header`,
defaultMessage: 'Next generation JavaScript',
},
javascriptMessage: {
id: `${scope}.javascript.message`,
defaultMessage: `Use template strings, object destructuring, arrow functions, JSX
syntax and more, today.`,
},
cssHeader: {
id: `${scope}.css.header`,
defaultMessage: 'Features',
},
cssMessage: {
id: `${scope}.css.message`,
defaultMessage: 'Next generation CSS',
},
routingHeader: {
id: `${scope}.routing.header`,
defaultMessage: 'Industry-standard routing',
},
routingMessage: {
id: `${scope}.routing.message`,
defaultMessage: `
Write composable CSS that's co-located with your components for
complete modularity. Unique generated class names keep the
specificity low while eliminating style clashes. Ship only the
styles that are on the page for the best performance.
`,
},
networkHeader: {
id: `${scope}.network.header`,
defaultMessage: 'Offline-first',
},
networkMessage: {
id: `${scope}.network.message`,
defaultMessage: `
The next frontier in performant web apps: availability without a
network connection from the instant your users load the app.
`,
},
intlHeader: {
id: `${scope}.internationalization.header`,
defaultMessage:
'Complete i18n Standard Internationalization & Pluralization',
},
intlMessage: {
id: `${scope}.internationalization.message`,
defaultMessage:
'Scalable apps need to support multiple languages, easily add and support multiple languages with `react-intl`.',
},
});
import React from 'react';
import { render } from 'react-testing-library';
import List from '../List';
describe('<List />', () => {
it('should render an <ul> tag', () => {
const {
container: { firstChild },
} = render(<List />);
expect(firstChild.tagName).toEqual('UL');
});
it('should have a class attribute', () => {
const {
container: { firstChild },
} = render(<List />);
expect(firstChild.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const {
container: { firstChild },
} = render(<List id={id} />);
expect(firstChild.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const {
container: { firstChild },
} = render(<List attribute="test" />);
expect(firstChild.hasAttribute('attribute')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import ListItem from '../ListItem';
describe('<ListItem />', () => {
it('should render an <li> tag', () => {
const {
container: { firstChild },
} = render(<ListItem />);
expect(firstChild.tagName).toEqual('LI');
});
it('should have a class attribute', () => {
const {
container: { firstChild },
} = render(<ListItem />);
expect(firstChild.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const {
container: { firstChild },
} = render(<ListItem id={id} />);
expect(firstChild.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const {
container: { firstChild },
} = render(<ListItem attribute="test" />);
expect(firstChild.hasAttribute('attribute')).toBe(false);
});
});
import React from 'react';
import { render } from 'react-testing-library';
import ListItemTitle from '../ListItemTitle';
describe('<ListItemTitle />', () => {
it('should render an <p> tag', () => {
const {
container: { firstChild },
} = render(<ListItemTitle />);
expect(firstChild.tagName).toEqual('P');
});
it('should have a class attribute', () => {
const {
container: { firstChild },
} = render(<ListItemTitle />);
expect(firstChild.hasAttribute('class')).toBe(true);
});
it('should adopt a valid attribute', () => {
const id = 'test';
const {
container: { firstChild },
} = render(<ListItemTitle id={id} />);
expect(firstChild.id).toEqual(id);
});
it('should not adopt an invalid attribute', () => {
const {
container: { firstChild },
} = render(<ListItemTitle attribute="test" />);
expect(firstChild.hasAttribute('attribute')).toBe(false);
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<FeaturePage /> should render its heading 1`] = `
<div>
<h1
class="H1-tncdg6-0 hpAKSC"
>
<span>
Features
</span>
</h1>
<ul
class="List-sc-1aai6b-0 gbaodI"
>
<li
class="ListItem-sc-27v6xc-0 fixMwZ"
>
<p
class="ListItemTitle-sc-1t4skn3-0 fWjsMZ"
>
<span>
Quick scaffolding
</span>
</p>
<p>
<span>
Automate the creation of components, containers, routes, selectors
and sagas - and their tests - right from the CLI!
</span>
</p>
</li>
<li
class="ListItem-sc-27v6xc-0 fixMwZ"
>
<p
class="ListItemTitle-sc-1t4skn3-0 fWjsMZ"
>
<span>
Instant feedback
</span>
</p>
<p>
<span>
Enjoy the best DX and code your app at the speed of thought! Your
saved changes to the CSS and JS are reflected instantaneously
without refreshing the page. Preserve application state even when
you update something in the underlying code!
</span>
</p>
</li>
<li
class="ListItem-sc-27v6xc-0 fixMwZ"
>
<p
class="ListItemTitle-sc-1t4skn3-0 fWjsMZ"
>
<span>
Industry-standard routing
</span>
</p>
<p>
<span>
Write composable CSS that's co-located with your components for
complete modularity. Unique generated class names keep the
specificity low while eliminating style clashes. Ship only the
styles that are on the page for the best performance.
</span>
</p>
</li>
<li
class="ListItem-sc-27v6xc-0 fixMwZ"
>
<p
class="ListItemTitle-sc-1t4skn3-0 fWjsMZ"
>
<span>
Offline-first
</span>
</p>
<p>
<span>
The next frontier in performant web apps: availability without a
network connection from the instant your users load the app.
</span>
</p>
</li>
<li
class="ListItem-sc-27v6xc-0 fixMwZ"
>
<p
class="ListItemTitle-sc-1t4skn3-0 fWjsMZ"
>
<span>
Complete i18n Standard Internationalization & Pluralization
</span>
</p>
<p>
<span>
Scalable apps need to support multiple languages, easily add and support multiple languages with \`react-intl\`.
</span>
</p>
</li>
</ul>
</div>
`;
import React from 'react';
import { render } from 'react-testing-library';
import { IntlProvider } from 'react-intl';
import FeaturePage from '../index';
describe('<FeaturePage />', () => {
it('should render its heading', () => {
const {
container: { firstChild },
} = render(
<IntlProvider locale="en">
<FeaturePage />
</IntlProvider>,
);
expect(firstChild).toMatchSnapshot();
});
});
import styled from 'styled-components';
const AtPrefix = styled.span`
color: black;
margin-left: 0.4em;
`;
export default AtPrefix;
import styled from 'styled-components';
import Section from './Section';
const CenteredSection = styled(Section)`
text-align: center;
`;
export default CenteredSection;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff was suppressed by a .gitattributes entry.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment