Commit 4e366050 authored by Julien Benchetrit's avatar Julien Benchetrit Committed by dengxiaofeng

merge version

parents
{
"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
}
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
const fs = require('fs');
const path = require('path');
const prettierOptions = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '.prettierrc'), 'utf8'),
);
module.exports = {
parser: 'babel-eslint',
extends: ['airbnb', 'prettier', 'prettier/react'],
plugins: ['prettier', 'redux-saga', 'react', 'react-hooks', 'jsx-a11y'],
env: {
jest: true,
browser: true,
node: true,
es6: true,
},
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
rules: {
'prettier/prettier': ['error', prettierOptions],
'arrow-body-style': [2, 'as-needed'],
'class-methods-use-this': 0,
'import/imports-first': 0,
'import/newline-after-import': 0,
'import/no-dynamic-require': 0,
'import/no-extraneous-dependencies': 0,
'import/no-named-as-default': 0,
'import/no-unresolved': 2,
'import/no-webpack-loader-syntax': 0,
'import/prefer-default-export': 0,
indent: [
2,
2,
{
SwitchCase: 1,
},
],
'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,
'max-len': 0,
'newline-per-chained-call': 0,
'no-confusing-arrow': 0,
'no-console': 1,
'no-unused-vars': 2,
'no-use-before-define': 0,
'prefer-template': 2,
'react/destructuring-assignment': 0,
'react-hooks/rules-of-hooks': 'error',
'react/jsx-closing-tag-location': 0,
'react/forbid-prop-types': 0,
'react/jsx-first-prop-new-line': [2, 'multiline'],
'react/jsx-filename-extension': 0,
'react/jsx-no-target-blank': 0,
'react/jsx-uses-vars': 2,
'react/require-default-props': 0,
'react/require-extension': 0,
'react/self-closing-comp': 0,
'react/sort-comp': 0,
'redux-saga/no-yield-in-race': 2,
'redux-saga/yield-effects': 2,
'require-yield': 0,
},
settings: {
'import/resolver': {
webpack: {
config: './internals/webpack/webpack.prod.babel.js',
},
},
},
};
# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes
# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto
#
# The above will handle all files NOT found below
#
#
## These files are text and should be normalized (Convert crlf => lf)
#
# source code
*.php text
*.css text
*.sass text
*.scss text
*.less text
*.styl text
*.js text eol=lf
*.coffee text
*.json text
*.htm text
*.html text
*.xml text
*.svg text
*.txt text
*.ini text
*.inc text
*.pl text
*.rb text
*.py text
*.scm text
*.sql text
*.sh text
*.bat text
# templates
*.ejs text
*.hbt text
*.jade text
*.haml text
*.hbs text
*.dot text
*.tmpl text
*.phtml text
# server config
.htaccess text
.nginx.conf text
# git config
.gitattributes text
.gitignore text
.gitconfig text
# code analysis config
.jshintrc text
.jscsrc text
.jshintignore text
.csslintrc text
# misc config
*.yaml text
*.yml text
.editorconfig text
# build config
*.npmignore text
*.bowerrc text
# Heroku
Procfile text
.slugignore text
# Documentation
*.md text
LICENSE text
AUTHORS text
#
## These files are binary and should be left untouched
#
# (binary is a macro for -text -diff)
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
*.eot binary
*.woff binary
*.pyc binary
*.pdf binary
---
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
# Don't check auto-generated stuff into git
coverage
build
node_modules
stats.json
# Cruft
.DS_Store
npm-debug.log
.idea
lts/dubnium
build/
node_modules/
internals/generators/
internals/scripts/
package-lock.json
yarn.lock
package.json
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}
{
"processors": ["stylelint-processor-styled-components"],
"extends": [
"stylelint-config-recommended",
"stylelint-config-styled-components"
]
}
language: node_js
node_js:
- 'node'
- 'lts/*'
script:
- node ./internals/scripts/generate-templates-for-linting
- npm test -- --maxWorkers=4
- npm run build
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
notifications:
email:
on_failure: change
after_success: 'npm run coveralls'
cache:
directories:
- node_modules
# 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.
The MIT License (MIT)
Copyright (c) 2019 Maximilian Stoiber
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This diff is collapsed.
<ifModule mod_rewrite.c>
#######################################################################
# GENERAL #
#######################################################################
# Make apache follow sym links to files
Options +FollowSymLinks
# If somebody opens a folder, hide all files from the resulting folder list
IndexIgnore */*
#######################################################################
# REWRITING #
#######################################################################
# Enable rewriting
RewriteEngine On
# If its not HTTPS
RewriteCond %{HTTPS} off
# Comment out the RewriteCond above, and uncomment the RewriteCond below if you're using a load balancer (e.g. CloudFlare) for SSL
# RewriteCond %{HTTP:X-Forwarded-Proto} !https
# Redirect to the same URL with https://, ignoring all further rules if this one is in effect
RewriteRule ^(.*) https://%{HTTP_HOST}/$1 [R,L]
# If we get to here, it means we are on https://
# If the file with the specified name in the browser doesn't exist
RewriteCond %{REQUEST_FILENAME} !-f
# and the directory with the specified name in the browser doesn't exist
RewriteCond %{REQUEST_FILENAME} !-d
# and we are not opening the root already (otherwise we get a redirect loop)
RewriteCond %{REQUEST_FILENAME} !\/$
# Rewrite all requests to the root
RewriteRule ^(.*) /
</ifModule>
<IfModule mod_headers.c>
# Do not cache sw.js, required for offline-first updates.
<FilesMatch "sw\.js$">
Header set Cache-Control "private, no-cache, no-store, proxy-revalidate, no-transform"
Header set Pragma "no-cache"
</FilesMatch>
</IfModule>
##
# Put this file in /etc/nginx/conf.d folder and make sure
# you have a line 'include /etc/nginx/conf.d/*.conf;'
# in your main nginx configuration file
##
##
# Redirect to the same URL with https://
##
server {
listen 80;
# Type your domain name below
server_name example.com;
return 301 https://$server_name$request_uri;
}
##
# HTTPS configurations
##
server {
listen 443 ssl;
# Type your domain name below
server_name example.com;
# Configure the Certificate and Key you got from your CA (e.g. Lets Encrypt)
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Only use TLS v1.2 as Transport Security Protocol
ssl_protocols TLSv1.2;
# Only use ciphersuites that are considered modern and secure by Mozilla
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
# Do not let attackers downgrade the ciphersuites in Client Hello
# Always use server-side offered ciphersuites
ssl_prefer_server_ciphers on;
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
# Uncomment if you want to use your own Diffie-Hellman parameter, which can be generated with: openssl ecparam -genkey -out dhparam.pem -name prime256v1
# See https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
# ssl_dhparam /path/to/dhparam.pem;
## OCSP Configuration START
# If you want to provide OCSP Stapling, you can uncomment the following lines
# See https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx for more infos about OCSP and its use case
# fetch OCSP records from URL in ssl_certificate and cache them
#ssl_stapling on;
#ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs (you will get this file from your CA)
#ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
## OCSP Configuration END
# To let nginx use its own DNS Resolver
# resolver <IP DNS resolver>;
# Always serve index.html for any request
location / {
# Set path
root /var/www/;
try_files $uri /index.html;
}
# Do not cache sw.js, required for offline-first updates.
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
##
# If you want to use Node/Rails/etc. API server
# on the same port (443) config Nginx as a reverse proxy.
# For security reasons use a firewall like ufw in Ubuntu
# and deny port 3000/tcp.
##
# location /api/ {
#
# proxy_pass http://localhost:3000;
# proxy_http_version 1.1;
# proxy_set_header X-Forwarded-Proto https;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
#
# }
}
/**
* app.js
*
* This is the entry file for the application, only setup and boilerplate
* code.
*/
// Needed for redux-saga es6 generator support
import '@babel/polyfill';
// Import all the third party stuff
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import FontFaceObserver from 'fontfaceobserver';
import history from 'utils/history';
import 'sanitize.css/sanitize.css';
// Import root app
import App from 'containers/App';
// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';
// Load the favicon and the .htaccess file
import '!file-loader?name=[name].[ext]!./images/favicon.ico';
import 'file-loader?name=.htaccess!./.htaccess'; // eslint-disable-line import/extensions
import configureStore from './configureStore';
// Import i18n messages
import { translationMessages } from './i18n';
// Observe loading of Open Sans (to remove open sans, remove the <link> tag in
// the index.html file and this observer)
const openSansObserver = new FontFaceObserver('Open Sans', {});
// When Open Sans is loaded, add a font-family using Open Sans to the body
openSansObserver.load().then(() => {
document.body.classList.add('fontLoaded');
});
// Create redux store with history
const initialState = {};
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE,
);
};
if (module.hot) {
// Hot reloadable React components and translation json files
// modules.hot.accept does not accept dynamic dependencies,
// have to be constants at compile-time
module.hot.accept(['./i18n', 'containers/App'], () => {
ReactDOM.unmountComponentAtNode(MOUNT_NODE);
render(translationMessages);
});
}
// Chunked polyfill for browsers without Intl support
if (!window.Intl) {
new Promise(resolve => {
resolve(import('intl'));
})
.then(() =>
Promise.all([
import('intl/locale-data/jsonp/en.js'),
import('intl/locale-data/jsonp/de.js'),
]),
) // eslint-disable-line prettier/prettier
.then(() => render(translationMessages))
.catch(err => {
throw err;
});
} else {
render(translationMessages);
}
// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
if (process.env.NODE_ENV === 'production') {
require('offline-plugin/runtime').install(); // eslint-disable-line global-require
}
/**
* 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();
});
});
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
export default function configureStore(initialState = {}, history) {
let composeEnhancers = compose;
const reduxSagaMonitorOptions = {};
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
/* eslint-disable no-underscore-dangle */
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
// NOTE: Uncomment the code below to restore support for Redux Saga
// Dev Tools once it supports redux-saga version 1.x.x
// if (window.__SAGA_MONITOR_EXTENSION__)
// reduxSagaMonitorOptions = {
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
// };
/* eslint-enable */
}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history)];
const enhancers = [applyMiddleware(...middlewares)];
const store = createStore(
createReducer(),
initialState,
composeEnhancers(...enhancers),
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}
/*
* App Actions
*
* Actions change things in your application
* Since this boilerplate uses a uni-directional data flow, specifically redux,
* we have these actions which are the only way your application interacts with
* your application state. This guarantees that your state is up to date and nobody
* messes it up weirdly somewhere.
*
* To add a new Action:
* 1) Import your constant
* 2) Add a function like this:
* export function yourAction(var) {
* return { type: YOUR_ACTION_CONSTANT, var: var }
* }
*/
import { LOAD_REPOS, LOAD_REPOS_SUCCESS, LOAD_REPOS_ERROR } from './constants';
/**
* Load the repositories, this action starts the request saga
*
* @return {object} An action object with a type of LOAD_REPOS
*/
export function loadRepos() {
return {
type: LOAD_REPOS,
};
}
/**
* Dispatched when the repositories are loaded by the request saga
*
* @param {array} repos The repository data
* @param {string} username The current username
*
* @return {object} An action object with a type of LOAD_REPOS_SUCCESS passing the repos
*/
export function reposLoaded(repos, username) {
return {
type: LOAD_REPOS_SUCCESS,
repos,
username,
};
}
/**
* Dispatched when loading the repositories fails
*
* @param {object} error The error
*
* @return {object} An action object with a type of LOAD_REPOS_ERROR passing the error
*/
export function repoLoadingError(error) {
return {
type: LOAD_REPOS_ERROR,
error,
};
}
/*
* AppConstants
* Each action has a corresponding type, which the reducer knows and picks up on.
* To avoid weird typos between the reducer and the actions, we save them as
* constants here. We prefix them with 'yourproject/YourComponent' so we avoid
* reducers accidentally picking up actions they shouldn't.
*
* Follow this format:
* export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
*/
export const LOAD_REPOS = 'boilerplate/App/LOAD_REPOS';
export const LOAD_REPOS_SUCCESS = 'boilerplate/App/LOAD_REPOS_SUCCESS';
export const LOAD_REPOS_ERROR = 'boilerplate/App/LOAD_REPOS_ERROR';
/**
*
* 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>
);
}
/*
* AppReducer
*
* The reducer takes care of our data. Using actions, we can
* update our application state. To add a new action,
* add it to the switch statement in the reducer function
*
*/
import produce from 'immer';
import { LOAD_REPOS_SUCCESS, LOAD_REPOS, LOAD_REPOS_ERROR } from './constants';
// The initial state of the App
export const initialState = {
loading: false,
error: false,
currentUser: false,
userData: {
repositories: false,
},
};
/* eslint-disable default-case, no-param-reassign */
const appReducer = (state = initialState, action) =>
produce(state, draft => {
switch (action.type) {
case LOAD_REPOS:
draft.loading = true;
draft.error = false;
draft.userData.repositories = false;
break;
case LOAD_REPOS_SUCCESS:
draft.userData.repositories = action.repos;
draft.loading = false;
draft.currentUser = action.username;
break;
case LOAD_REPOS_ERROR:
draft.error = action.error;
draft.loading = false;
break;
}
});
export default appReducer;
/**
* The global state selectors
*/
import { createSelector } from 'reselect';
import { initialState } from './reducer';
const selectGlobal = state => state.global || initialState;
const selectRouter = state => state.router;
const makeSelectCurrentUser = () =>
createSelector(
selectGlobal,
globalState => globalState.currentUser,
);
const makeSelectLoading = () =>
createSelector(
selectGlobal,
globalState => globalState.loading,
);
const makeSelectError = () =>
createSelector(
selectGlobal,
globalState => globalState.error,
);
const makeSelectRepos = () =>
createSelector(
selectGlobal,
globalState => globalState.userData.repositories,
);
const makeSelectLocation = () =>
createSelector(
selectRouter,
routerState => routerState.location,
);
export {
selectGlobal,
makeSelectCurrentUser,
makeSelectLoading,
makeSelectError,
makeSelectRepos,
makeSelectLocation,
};
// 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>
`;
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 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.
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