Merge branch 'staging' into fix/normalize-flash-type

This commit is contained in:
Cassidy Pignatello
2018-01-10 14:19:00 -05:00
committed by GitHub
117 changed files with 42131 additions and 2328 deletions

View File

@@ -2,6 +2,15 @@
"presets": ["es2015", "react", "stage-0"], "presets": ["es2015", "react", "stage-0"],
"plugins": [ "plugins": [
"babel-plugin-add-module-exports", "babel-plugin-add-module-exports",
"lodash" "lodash",
[
"transform-imports", {
"react-bootstrap": {
"transform": "react-bootstrap/lib/${member}",
"preventFullImport": true
}
}
]
] ]
} }

2
.gitignore vendored
View File

@@ -53,6 +53,6 @@ public/js/vendor*
public/js/faux* public/js/faux*
public/js/frame-runner* public/js/frame-runner*
public/css/main* public/css/main*
webpack-bundle-stats.html
server/rev-manifest.json server/rev-manifest.json
google-credentials.json google-credentials.json

View File

@@ -19,20 +19,19 @@ Working on your first Pull Request? You can learn how from this *free* series [H
## Contribution Guidelines ## Contribution Guidelines
- [Prerequisites](#prerequisites) - [Prerequisites](#prerequisites)
- [Forking The Project](#forking-the-project) - [Forking the Project](#forking-the-project)
- [Create A Branch](#create-a-branch) - [Create a Branch](#create-a-branch)
- [Setup Linting](#setup-linting) - [Set Up Linting](#set-up-linting)
- [Setup freeCodeCamp](#setup-freecodecamp) - [Set Up MailHog](#set-up-mailhog)
- [Set Up freeCodeCamp](#set-up-freecodecamp)
- [Make Changes](#make-changes) - [Make Changes](#make-changes)
- [Run The Test Suite](#run-the-test-suite) - [Run The Test Suite](#run-the-test-suite)
- [Squash Your Commits](#squash-your-commits) - [Creating a Pull Request](#creating-a-pull-request)
- [Commit Message](#commit-message)
- [Creating A Pull Request](#creating-a-pull-request)
- [Common Steps](#common-steps) - [Common Steps](#common-steps)
- [How We Review and Merge Pull Requests](#how-we-review-and-merge-pull-requests) - [How We Review and Merge Pull Requests](#how-we-review-and-merge-pull-requests)
- [How We Close Stale Issues](#how-we-close-stale-issues) - [How We Close Stale Issues](#how-we-close-stale-issues)
- [Next Steps](#next-steps) - [Next Steps](#next-steps)
- [Other resources](#other-resources) - [Other Resources](#other-resources)
### Prerequisites ### Prerequisites
@@ -40,12 +39,12 @@ Working on your first Pull Request? You can learn how from this *free* series [H
| ------------------------------------------- | ------- | | ------------------------------------------- | ------- |
| [MongoDB Community Server](https://docs.mongodb.com/manual/administration/install-community/) | `~ ^3` | | [MongoDB Community Server](https://docs.mongodb.com/manual/administration/install-community/) | `~ ^3` |
| [MailHog](https://github.com/mailhog/MailHog) | `~ ^1` | | [MailHog](https://github.com/mailhog/MailHog) | `~ ^1` |
| [Node.js](http://nodejs.org) | `~ ^8` | | [Node.js](http://nodejs.org) | `~ ^8.9.3` |
| npm (comes with Node) | `~ ^5` | | npm (comes with Node) | `~ ^5` |
> _Updating to the latest releases is recommended_. > _Updating to the latest releases is recommended_.
If Node or MongoDB is already installed in your machine, run the following commands to validate the versions: If Node.js or MongoDB is already installed on your machine, run the following commands to validate the versions:
```shell ```shell
node -v node -v
@@ -60,19 +59,18 @@ Platform-specific guides to setting up a development environment:
- [How to clone and setup the freeCodeCamp website on a Windows pc](https://forum.freecodecamp.org/t/how-to-clone-and-setup-the-free-code-camp-website-on-a-windows-pc/19366) - [How to clone and setup the freeCodeCamp website on a Windows pc](https://forum.freecodecamp.org/t/how-to-clone-and-setup-the-free-code-camp-website-on-a-windows-pc/19366)
- [How to Clone and Setup the freeCodeCamp Website on a Mac](https://forum.freecodecamp.org/t/how-to-clone-and-setup-the-freecodecamp-website-on-a-mac/78450) - [How to Clone and Setup the freeCodeCamp Website on a Mac](https://forum.freecodecamp.org/t/how-to-clone-and-setup-the-freecodecamp-website-on-a-mac/78450)
### Forking The Project ### Forking the Project
#### Setting Up Your System #### Setting Up Your System
1. Install [Git](https://git-scm.com/) or your favorite Git client. 1. Install [Git](https://git-scm.com/) or your favorite Git client.
2. (Optional) [Setup an SSH Key](https://help.github.com/articles/generating-an-ssh-key/) for GitHub. 2. (Optional) [Setup an SSH Key](https://help.github.com/articles/generating-an-ssh-key/) for GitHub.
3. Create a parent projects directory on your system. For this guide, it will be assumed that it is `/mean/`
#### Forking freeCodeCamp #### Forking freeCodeCamp
1. Go to the top level freeCodeCamp repository: <https://github.com/freeCodeCamp/freeCodeCamp> 1. Go to the top level freeCodeCamp repository: <https://github.com/freeCodeCamp/freeCodeCamp>
2. Click the "Fork" Button in the upper right hand corner of the interface ([More Details Here](https://help.github.com/articles/fork-a-repo/)) 2. Click the "Fork" Button in the upper right hand corner of the interface ([More Details Here](https://help.github.com/articles/fork-a-repo/))
3. After the repository has been forked, you will be taken to your copy of the FCC repo at `yourUsername/freeCodeCamp` 3. After the repository (repo) has been forked, you will be taken to your copy of the freeCodeCamp repo at <https://github.com/yourUsername/freeCodeCamp>
#### Cloning Your Fork #### Cloning Your Fork
@@ -83,26 +81,26 @@ Platform-specific guides to setting up a development environment:
$ git clone https://github.com/yourUsername/freeCodeCamp.git $ git clone https://github.com/yourUsername/freeCodeCamp.git
``` ```
##### (make sure to replace `yourUsername` with your GitHub Username) **(make sure to replace `yourUsername` with your GitHub username)**
This will download the entire FCC repo to your projects directory. This will download the entire freeCodeCamp repo to your projects directory.
#### Setup Your Upstream #### Setup Your Upstream
1. Change directory to the new freeCodeCamp directory (`cd freeCodeCamp`) 1. Change directory to the new freeCodeCamp directory (`cd freeCodeCamp`)
2. Add a remote to the official FCC repo: 2. Add a remote to the official freeCodeCamp repo:
```shell ```shell
$ git remote add upstream https://github.com/freeCodeCamp/freeCodeCamp.git $ git remote add upstream https://github.com/freeCodeCamp/freeCodeCamp.git
``` ```
Congratulations, you now have a local copy of the FCC repo! Congratulations, you now have a local copy of the freeCodeCamp repo!
#### Maintaining Your Fork #### Maintaining Your Fork
Now that you have a copy of your fork, there is work you will need to do to keep it current. Now that you have a copy of your fork, there is work you will need to do to keep it current.
##### **Rebasing from Upstream** ##### Rebasing from Upstream
Do this prior to every time you create a branch for a PR: Do this prior to every time you create a branch for a PR:
@@ -135,7 +133,7 @@ $ git push origin staging --force
This will overwrite the staging branch of your fork. This will overwrite the staging branch of your fork.
### Create A Branch ### Create a Branch
Before you start working, you will need to create a separate branch specific to the issue / feature you're working on. You will push your work to this branch. Before you start working, you will need to create a separate branch specific to the issue / feature you're working on. You will push your work to this branch.
@@ -157,22 +155,73 @@ and to push to GitHub:
$ git push origin [name_of_your_new_branch] $ git push origin [name_of_your_new_branch]
``` ```
##### If you need more help with branching, take a look at _[this](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)_. **If you need more help with branching, take a look at [this](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches).**
### Setup Linting ### Set Up Linting
You should have [ESLint running in your editor](http://eslint.org/docs/user-guide/integrations.html), and it will highlight anything doesn't conform to [freeCodeCamp's JavaScript Style Guide](http://forum.freecodecamp.org/t/free-code-camp-javascript-style-guide/19121) (you can find a summary of those rules [here](https://github.com/freeCodeCamp/freeCodeCamp/blob/staging/.eslintrc)). You should have [ESLint running in your editor](http://eslint.org/docs/user-guide/integrations.html), and it will highlight anything doesn't conform to [freeCodeCamp's JavaScript Style Guide](http://forum.freecodecamp.org/t/free-code-camp-javascript-style-guide/19121) (you can find a summary of those rules [here](https://github.com/freeCodeCamp/freeCodeCamp/blob/staging/.eslintrc)).
> Please do not ignore any linting errors, as they are meant to **help** you and to ensure a clean and simple code base. > Please do not ignore any linting errors, as they are meant to **help** you and to ensure a clean and simple code base.
### Set Up MailHog
To be able to log in, you need to set up MailHog. MailHog is a local SMTP mail server that will catch the emails your freeCodeCamp instance is sending. How you install MailHog is dependent upon your OS.
#### macOS
Here is how to set up MailHog on macOS with [Homebrew](https://brew.sh/):
```bash
brew install mailhog
brew services start mailhog
```
#### Windows
Download the latest MailHog version from [MailHog's official repository](https://github.com/mailhog/MailHog/blob/master/docs/RELEASES.md). Click on the link for your Windows version (32 or 64 bit) and .exe file will be downloaded to your computer.
Once it finishes downloading, click on the file. You will probably get a Windows firewall notification where you will have to allow access to MailHog. Once you do, a standard Windows command line prompt will open with MailHog already running.
To close MailHog, close the command prompt. To run it again, click on the same .exe file. You don't need to download a new one.
#### Linux
First install Go.
For Debian-based systems like Ubuntu and Linux Mint, run:
```bash
sudo apt-get install golang
```
For CentOS, Fedora, Red Hat Linux, and other RPM-based systems, run:
```bash
sudo dnf install golang
```
Or:
```bash
sudo yum install golang
```
Then install and run MailHog:
```bash
go get github.com/mailhog/MailHog
MailHog
```
To access your MailHog inbox, open your browser and navigate to [http://localhost:8025](http://localhost:8025). For any other questions related to MailHog or for instructions on custom configurations, check out the [MailHog](https://github.com/mailhog/MailHog) repository.
### Set Up freeCodeCamp
### Setup freeCodeCamp
Once you have freeCodeCamp cloned, before you start the application, you first need to install all of the dependencies: Once you have freeCodeCamp cloned, before you start the application, you first need to install all of the dependencies:
```bash ```bash
# Install NPM dependencies # Install NPM dependencies
npm install npm install
``` ```
Then you need to add the private environment variables (API Keys): Then you need to add the private environment variables (API Keys):
@@ -180,7 +229,12 @@ Then you need to add the private environment variables (API Keys):
```bash ```bash
# Create a copy of the "sample.env" and name it as ".env". # Create a copy of the "sample.env" and name it as ".env".
# Populate it with the necessary API keys and secrets: # Populate it with the necessary API keys and secrets:
# macOS / Linux
cp sample.env .env cp sample.env .env
# Windows
copy sample.env .env
``` ```
Then edit the `.env` file and modify the API keys only for services that you will use. Then edit the `.env` file and modify the API keys only for services that you will use.
@@ -188,14 +242,6 @@ Note: Not all keys are required, to run the app locally, however `MONGOHQ_URL` i
You can leave the other keys as they are. Keep in mind if you want to use more services you'll have to get your own API keys for those services and edit those entries accordingly in the .env file. You can leave the other keys as they are. Keep in mind if you want to use more services you'll have to get your own API keys for those services and edit those entries accordingly in the .env file.
Next you should setup MailHog, a local SMTP mail server that will catch all the outgoing freeCodeCamp messages generated locally. How you start up MailHog is dependent upon your OS, but here's an example for MacOS with Brew.
```bash
brew services start mailhog
```
To access your MailHog inbox, open your browser and navigate to [http://localhost:8025](http://localhost:8025). For any other questions related to MailHog or for instructions on custom configurations, check out the [MailHog](https://github.com/mailhog/MailHog) repository.
Now you will need to start MongoDB, and then seed the database, then you can start the application: Now you will need to start MongoDB, and then seed the database, then you can start the application:
```bash ```bash
@@ -212,14 +258,14 @@ mongod
# This command should only be run once. # This command should only be run once.
npm run only-once npm run only-once
# start the application # Start the application
npm run develop npm run develop
``` ```
Now navigate to your browser and open Now navigate to your browser and open <http://localhost:3000>. If the app loads, congratulations you're all set.
<http://localhost:3000>. If the app loads, Otherwise, let us know by asking in the [Contributors chat room](https://gitter.im/FreeCodeCamp/Contributors) on Gitter. There might be an error in the console of your browser or in Bash / Terminal / Command Line that will help identify the problem.
congratulations you're all set. Otherwise, let us know by asking in the [Contributors chat room](https://gitter.im/FreeCodeCamp/Contributors) on Gitter. There also might be an error in the console of your browser or in Bash / Terminal / Command Line that will help identify the problem. If the app launches but you are encountering errors with the UI itself, for example if fonts are not being loaded or if the code editor is not displaying properly, you may try the following:
If the app launches but you are encountering errors with the UI itself, for example if fonts are not being loaded or if the code editor is not displaying properly, you may try the following:
```bash ```bash
# Remove all installed node modules # Remove all installed node modules
rm -rf node_modules rm -rf node_modules
@@ -227,7 +273,7 @@ rm -rf node_modules
# Reinstall npm packages # Reinstall npm packages
npm install npm install
# Seed the database (optional) # Seed the database
node seed node seed
# Re-start the application # Re-start the application
@@ -235,9 +281,10 @@ npm run develop
``` ```
### Make Changes ### Make Changes
This bit is up to you! This bit is up to you!
#### How to find the code in the freeCodeCamp codebase to fix/edit? #### How to find the code in the freeCodeCamp codebase to fix/edit
The best way to find out any code you wish to change/add or remove is using The best way to find out any code you wish to change/add or remove is using
the GitHub search bar at the top of the repository page. For example, you could the GitHub search bar at the top of the repository page. For example, you could
@@ -247,6 +294,7 @@ that you were looking forward to edit. Always feel free to reach out to the chat
room when you are not certain of any thing specific in the code. room when you are not certain of any thing specific in the code.
#### Changes to the seed files #### Changes to the seed files
If you made changes to any file in the `/seed` directory, you need to run If you made changes to any file in the `/seed` directory, you need to run
```shell ```shell
$ node seed $ node seed
@@ -254,6 +302,7 @@ $ node seed
in order to see the changes. in order to see the changes.
### Run The Test Suite ### Run The Test Suite
When you're ready to share your code, run the test suite: When you're ready to share your code, run the test suite:
```shell ```shell
@@ -262,34 +311,12 @@ $ npm test
and ensure all tests pass. and ensure all tests pass.
### Squash Your Commits ### Creating a Pull Request
When you make a pull request, all of your changes need to be in one commit.
If you have made more than one commit, then you will need to _squash_ your commits.
To do this, see [Squashing Your Commits](http://forum.freecodecamp.org/t/how-to-squash-multiple-commits-into-one-with-git/13231).
### Commit Message
When you commit your changes, please use conventional commit messages.
The commit message should be structured as follows:
```
<type>[optional scope]: <description>
[optional body]
[optional footer]
```
For help writing your commit message, execute `npm run commit` from the command line and the [commitizen](http://commitizen.github.io/cz-cli/) CLI tool will assist you in creating a conventional commit message.
Learn more at [Conventional Commits](http://conventionalcommits.org).
### Creating A Pull Request
#### What is a Pull Request? #### What is a Pull Request?
A pull request (PR) is a method of submitting proposed changes to the freeCodeCamp A pull request (PR) is a method of submitting proposed changes to the freeCodeCamp
Repo (or any Repo, for that matter). You will make changes to copies of the repo (or any repo, for that matter). You will make changes to copies of the
files which make up freeCodeCamp in a personal fork, then apply to have them files which make up freeCodeCamp in a personal fork, then apply to have them
accepted by freeCodeCamp proper. accepted by freeCodeCamp proper.
@@ -345,20 +372,16 @@ nothing to commit, working directory clean
add .` to add all unstaged files. Take care, though, because you can add .` to add all unstaged files. Take care, though, because you can
accidentally add files you don't want added. Review your `git status` first. accidentally add files you don't want added. Review your `git status` first.
6. Commit your edits (follow any one of the below methods): 6. Commit your edits: We have a [tool](https://commitizen.github.io/cz-cli/)
that helps you to make standard commit messages. Execute `npm run commit`
and follow the steps.
a. Using the inbuilt script (_recommended_): 7. [Squash your commits](http://forum.freecodecamp.org/t/how-to-squash-multiple-commits-into-one-with-git/13231) if there are more than one.
- We have a [tool](https://commitizen.github.io/cz-cli/) that helps you to make standard commit messages. Simply execute `npm run commit` after you have added the necessary files as mentioned in the step earlier.
b. Using Commitizen CLI: 8. If you would want to add/remove changes to previous commit, add the files as in Step 5 earlier,
- If you are already using [commitizen](http://commitizen.github.io/cz-cli/), simply doing a `git cz` works as expected too!
7. Squash your commits, if there are more than one.
8. If you would want to add/remove changes to previous commit simply add the files as in Step 5 earlier,
and use `git commit --amend` or `git commit --amend --no-edit` (for keeping the same commit message). and use `git commit --amend` or `git commit --amend --no-edit` (for keeping the same commit message).
9. Push your commits to your GitHub Fork: `git push -u origin branch/name-here` 9. Push your commits to your GitHub Fork: `git push origin branch/name-here`
10. Go to [Common Steps](#common-steps) 10. Go to [Common Steps](#common-steps)
@@ -377,7 +400,7 @@ for further information
1. Once the edits have been committed, you will be prompted to create a pull 1. Once the edits have been committed, you will be prompted to create a pull
request on your fork's GitHub Page. request on your fork's GitHub Page.
2. By default, all pull requests should be against the FCC main repo, `staging` 2. By default, all pull requests should be against the freeCodeCamp main repo, `staging`
branch. branch.
3. Submit a [pull 3. Submit a [pull
@@ -402,7 +425,6 @@ for further information
6. Indicate if you have tested on a local copy of the site or not. 6. Indicate if you have tested on a local copy of the site or not.
### How We Review and Merge Pull Requests ### How We Review and Merge Pull Requests
freeCodeCamp has a team of volunteer Issue Moderators. These Issue Moderators routinely go through open pull requests in a process called [Quality Assurance](https://en.wikipedia.org/wiki/Quality_assurance) (QA). freeCodeCamp has a team of volunteer Issue Moderators. These Issue Moderators routinely go through open pull requests in a process called [Quality Assurance](https://en.wikipedia.org/wiki/Quality_assurance) (QA).
@@ -413,7 +435,6 @@ freeCodeCamp has a team of volunteer Issue Moderators. These Issue Moderators ro
If you would like to apply to join our Issue Moderator team - which is a Core Team position - message [@BerkeleyTrue](https://gitter.im/berkeleytrue) with links to 5 of your pull requests that have been accepted and 5 issues where you have helped someone else through commenting or QA'ing. If you would like to apply to join our Issue Moderator team - which is a Core Team position - message [@BerkeleyTrue](https://gitter.im/berkeleytrue) with links to 5 of your pull requests that have been accepted and 5 issues where you have helped someone else through commenting or QA'ing.
### How We Close Stale Issues ### How We Close Stale Issues
We will close any issues or pull requests that have been inactive for more than 15 days, except those that match the following criteria: We will close any issues or pull requests that have been inactive for more than 15 days, except those that match the following criteria:
@@ -447,7 +468,7 @@ overwrite your old commit: `git push --force`
Be sure to post in the PR conversation that you have made the requested changes. Be sure to post in the PR conversation that you have made the requested changes.
### Other resources ### Other Resources
- [Style Guide for freeCodeCamp - [Style Guide for freeCodeCamp
Challenges](https://github.com/freeCodeCamp/freeCodeCamp/blob/staging/seed/challenge-style-guide.md) Challenges](https://github.com/freeCodeCamp/freeCodeCamp/blob/staging/seed/challenge-style-guide.md)
@@ -464,11 +485,11 @@ Be sure to post in the PR conversation that you have made the requested changes.
- [How to clone the freeCodeCamp website on a Windows - [How to clone the freeCodeCamp website on a Windows
pc](http://forum.freecodecamp.org/t/how-to-clone-and-setup-the-free-code-camp-website-on-a-windows-pc/19366) pc](http://forum.freecodecamp.org/t/how-to-clone-and-setup-the-free-code-camp-website-on-a-windows-pc/19366)
- [How to log in to your local FCC site - using - [How to log in to your local freeCodeCamp site - using
GitHub](http://forum.freecodecamp.org/t/how-to-log-in-to-your-local-instance-of-free-code-camp/19552) GitHub](http://forum.freecodecamp.org/t/how-to-log-in-to-your-local-instance-of-free-code-camp/19552)
- [Writing great git commit - [Writing great git commit
messages](http://forum.freecodecamp.org/t/writing-good-git-commit-messages/13210) messages](http://forum.freecodecamp.org/t/writing-good-git-commit-messages/13210)
- [Contributor Chat Support - For the FCC Repositories, and running a local - [Contributor Chat Support - For the freeCodeCamp repositories, and running a local
instance](https://gitter.im/FreeCodeCamp/Contributors) instance](https://gitter.im/FreeCodeCamp/Contributors)

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License BSD 3-Clause License
Copyright (c) 2017, freeCodeCamp. Copyright (c) 2018, freeCodeCamp.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@@ -66,7 +66,7 @@ We welcome pull requests from freeCodeCamp campers (our students) and seasoned J
License License
------- -------
Copyright (c) 2017 freeCodeCamp. Copyright (c) 2018 freeCodeCamp.
The content of this repository bound by the following LICENSE(S) The content of this repository bound by the following LICENSE(S)
- The computer software is licensed under the [BSD-3-Clause](./LICENSE.md). - The computer software is licensed under the [BSD-3-Clause](./LICENSE.md).

View File

@@ -81,10 +81,6 @@
border-bottom: 1px solid @modal-header-border-color; border-bottom: 1px solid @modal-header-border-color;
min-height: (@modal-title-padding + @modal-title-line-height); min-height: (@modal-title-padding + @modal-title-line-height);
} }
// Close icon
.modal-header .close {
margin-top: -2px;
}
// Title text within header // Title text within header
.modal-title { .modal-title {

View File

@@ -14,6 +14,7 @@
@gray: lighten(@gray-base, 33.5%); // #555 @gray: lighten(@gray-base, 33.5%); // #555
@gray-light: lighten(@gray-base, 46.7%); // #777 @gray-light: lighten(@gray-base, 46.7%); // #777
@gray-lighter: lighten(@gray-base, 93.5%); // #eee @gray-lighter: lighten(@gray-base, 93.5%); // #eee
@silver-chalice: lighten(@gray-base, 45%); // #a6a6a6
@brand-primary: darkgreen; @brand-primary: darkgreen;
@brand-success: #457E86; @brand-success: #457E86;
@@ -41,7 +42,8 @@
//## Night mode styles //## Night mode styles
@night-body-bg: @gray-dark; @night-body-bg: @gray-dark;
@night-text-color: #ccc; @night-text-color: #ccc;
@night-search-color: #044421;
//== Typography //== Typography
// //

View File

@@ -500,7 +500,7 @@ form.update-email .btn{
} }
} }
.challenge-list-header { .challenges-list-header {
background-color: @brand-primary; background-color: @brand-primary;
color: @gray-lighter; color: @gray-lighter;
font-size: 36px; font-size: 36px;
@@ -825,7 +825,7 @@ code {
color: @night-text-color; color: @night-text-color;
.btn-group, .btn-group,
.text-success, .text-success,
.challenge-list-header, .challenges-list-header,
.fcc-footer { .fcc-footer {
background-color: @night-body-bg; background-color: @night-body-bg;
} }

View File

@@ -6,12 +6,11 @@ import FA from 'react-fontawesome';
import { Panel } from 'react-bootstrap'; import { Panel } from 'react-bootstrap';
import ns from './ns.json'; import ns from './ns.json';
import Challenge from './Challenge.jsx'; import Challenges from './Challenges.jsx';
import { import {
toggleThisPanel, toggleThisPanel,
makePanelOpenSelector, makePanelOpenSelector
makePanelHiddenSelector
} from './redux'; } from './redux';
import { makeBlockSelector } from '../entities'; import { makeBlockSelector } from '../entities';
@@ -21,15 +20,13 @@ function makeMapStateToProps(_, { dashedName }) {
return createSelector( return createSelector(
makeBlockSelector(dashedName), makeBlockSelector(dashedName),
makePanelOpenSelector(dashedName), makePanelOpenSelector(dashedName),
makePanelHiddenSelector(dashedName), (block, isOpen) => {
(block, isOpen, isHidden) => {
return { return {
isOpen, isOpen,
isHidden,
dashedName, dashedName,
title: block.title, title: block.title,
time: block.time, time: block.time,
challenges: block.challenges challenges: block.challenges || []
}; };
} }
); );
@@ -37,7 +34,6 @@ function makeMapStateToProps(_, { dashedName }) {
const propTypes = { const propTypes = {
challenges: PropTypes.array, challenges: PropTypes.array,
dashedName: PropTypes.string, dashedName: PropTypes.string,
isHidden: PropTypes.bool,
isOpen: PropTypes.bool, isOpen: PropTypes.bool,
time: PropTypes.string, time: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
@@ -70,30 +66,14 @@ export class Block extends PureComponent {
); );
} }
renderChallenges(challenges) {
if (!Array.isArray(challenges) || !challenges.length) {
return <div>No Challenges Found</div>;
}
return challenges.map(dashedName => (
<Challenge
dashedName={ dashedName }
key={ dashedName }
/>
));
}
render() { render() {
const { const {
title, title,
time, time,
dashedName, dashedName,
isOpen, isOpen,
isHidden,
challenges challenges
} = this.props; } = this.props;
if (isHidden) {
return null;
}
return ( return (
<Panel <Panel
bsClass={ `${ns}-accordion-panel` } bsClass={ `${ns}-accordion-panel` }
@@ -105,7 +85,7 @@ export class Block extends PureComponent {
key={ title } key={ title }
onSelect={ this.handleSelect } onSelect={ this.handleSelect }
> >
{ this.renderChallenges(challenges) } { isOpen && <Challenges challenges={ challenges } /> }
</Panel> </Panel>
); );
} }

39
common/app/Map/Blocks.jsx Normal file
View File

@@ -0,0 +1,39 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ns from './ns.json';
import Block from './Block.jsx';
const propTypes = {
blocks: PropTypes.array.isRequired
};
export default class Blocks extends Component {
shouldComponentUpdate(nextProps) {
return this.props.blocks !== nextProps.blocks;
}
render() {
const {
blocks
} = this.props;
if (blocks.length <= 0) {
return null;
}
return (
<div className={ `${ns}-accordion-block` }>
{
blocks.map(dashedName => (
<Block
dashedName={ dashedName }
key={ dashedName }
/>
))
}
</div>
);
}
}
Blocks.displayName = 'Blocks';
Blocks.propTypes = propTypes;

View File

@@ -5,11 +5,7 @@ import { createSelector } from 'reselect';
import classnames from 'classnames'; import classnames from 'classnames';
import debug from 'debug'; import debug from 'debug';
import { import { clickOnChallenge } from './redux';
clickOnChallenge,
makePanelHiddenSelector
} from './redux';
import { userSelector } from '../redux'; import { userSelector } from '../redux';
import { challengeMapSelector } from '../entities'; import { challengeMapSelector } from '../entities';
import { Link } from '../Router'; import { Link } from '../Router';
@@ -23,7 +19,6 @@ const propTypes = {
isComingSoon: PropTypes.bool, isComingSoon: PropTypes.bool,
isCompleted: PropTypes.bool, isCompleted: PropTypes.bool,
isDev: PropTypes.bool, isDev: PropTypes.bool,
isHidden: PropTypes.bool,
isLocked: PropTypes.bool, isLocked: PropTypes.bool,
isRequired: PropTypes.bool, isRequired: PropTypes.bool,
title: PropTypes.string title: PropTypes.string
@@ -34,11 +29,9 @@ function makeMapStateToProps(_, { dashedName }) {
return createSelector( return createSelector(
userSelector, userSelector,
challengeMapSelector, challengeMapSelector,
makePanelHiddenSelector(dashedName),
( (
{ challengeMap: userChallengeMap }, { challengeMap: userChallengeMap },
challengeMap, challengeMap
isHidden
) => { ) => {
const { const {
id, id,
@@ -51,7 +44,6 @@ function makeMapStateToProps(_, { dashedName }) {
const isCompleted = userChallengeMap ? !!userChallengeMap[id] : false; const isCompleted = userChallengeMap ? !!userChallengeMap[id] : false;
return { return {
dashedName, dashedName,
isHidden,
isCompleted, isCompleted,
title, title,
block, block,
@@ -115,12 +107,11 @@ export class Challenge extends PureComponent {
isComingSoon, isComingSoon,
isCompleted, isCompleted,
isDev, isDev,
isHidden,
isLocked, isLocked,
isRequired, isRequired,
title title
} = this.props; } = this.props;
if (isHidden || !title) { if (!title) {
return null; return null;
} }
const challengeClassName = classnames({ const challengeClassName = classnames({

View File

@@ -0,0 +1,35 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Challenge from './Challenge.jsx';
const propTypes = {
challenges: PropTypes.array.isRequired
};
export default class Challenges extends Component {
shouldComponentUpdate(nextProps) {
return this.props.challenges !== nextProps.challenges;
}
render() {
const { challenges } = this.props;
if (!challenges.length) {
return <div>No Challenges Found</div>;
}
return (
<div>
{
challenges.map(dashedName => (
<Challenge
dashedName={ dashedName }
key={ dashedName }
/>
))
}
</div>
);
}
}
Challenges.displayName = 'Challenges';
Challenges.propTypes = propTypes;

View File

@@ -6,28 +6,25 @@ import FA from 'react-fontawesome';
import { Panel } from 'react-bootstrap'; import { Panel } from 'react-bootstrap';
import ns from './ns.json'; import ns from './ns.json';
import Block from './Block.jsx'; import Blocks from './Blocks.jsx';
import { import {
toggleThisPanel, toggleThisPanel,
makePanelOpenSelector, makePanelOpenSelector
makePanelHiddenSelector
} from './redux'; } from './redux';
import { makeSuperBlockSelector } from '../entities'; import { makeSuperBlockSelector } from '../entities';
const dispatchActions = { toggleThisPanel }; const mapDispatchToProps = { toggleThisPanel };
// make selectors unique to each component // make selectors unique to each component
// see // see
// reactjs/reselect // reactjs/reselect
// sharing-selectors-with-props-across-multiple-components // sharing-selectors-with-props-across-multiple-components
function makeMapStateToProps(_, { dashedName }) { function mapStateToProps(_, { dashedName }) {
return createSelector( return createSelector(
makeSuperBlockSelector(dashedName), makeSuperBlockSelector(dashedName),
makePanelOpenSelector(dashedName), makePanelOpenSelector(dashedName),
makePanelHiddenSelector(dashedName), (superBlock, isOpen) => ({
(superBlock, isOpen, isHidden) => ({
isOpen, isOpen,
isHidden,
dashedName, dashedName,
title: superBlock.title || dashedName, title: superBlock.title || dashedName,
blocks: superBlock.blocks || [], blocks: superBlock.blocks || [],
@@ -39,7 +36,6 @@ function makeMapStateToProps(_, { dashedName }) {
const propTypes = { const propTypes = {
blocks: PropTypes.array, blocks: PropTypes.array,
dashedName: PropTypes.string, dashedName: PropTypes.string,
isHidden: PropTypes.bool,
isOpen: PropTypes.bool, isOpen: PropTypes.bool,
message: PropTypes.string, message: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
@@ -56,18 +52,6 @@ export class SuperBlock extends PureComponent {
this.props.toggleThisPanel(eventKey); this.props.toggleThisPanel(eventKey);
} }
renderBlocks(blocks) {
if (!Array.isArray(blocks) || !blocks.length) {
return null;
}
return blocks.map(dashedName => (
<Block
dashedName={ dashedName }
key={ dashedName }
/>
));
}
renderMessage(message) { renderMessage(message) {
if (!message) { if (!message) {
return null; return null;
@@ -98,12 +82,8 @@ export class SuperBlock extends PureComponent {
dashedName, dashedName,
blocks, blocks,
message, message,
isOpen, isOpen
isHidden
} = this.props; } = this.props;
if (isHidden) {
return null;
}
return ( return (
<Panel <Panel
bsClass={ `${ns}-accordion-panel` } bsClass={ `${ns}-accordion-panel` }
@@ -116,9 +96,7 @@ export class SuperBlock extends PureComponent {
onSelect={ this.handleSelect } onSelect={ this.handleSelect }
> >
{ this.renderMessage(message) } { this.renderMessage(message) }
<div className={ `${ns}-accordion-block` }> <Blocks blocks={ blocks } />
{ this.renderBlocks(blocks) }
</div>
</Panel> </Panel>
); );
} }
@@ -128,6 +106,6 @@ SuperBlock.displayName = 'SuperBlock';
SuperBlock.propTypes = propTypes; SuperBlock.propTypes = propTypes;
export default connect( export default connect(
makeMapStateToProps, mapStateToProps,
dispatchActions mapDispatchToProps
)(SuperBlock); )(SuperBlock);

View File

@@ -98,7 +98,7 @@
} }
.@{ns}-block-time { .@{ns}-block-time {
color: #BBBBBB; color: #555555;
@media (min-width: 721px) { @media (min-width: 721px) {
float: right; float: right;
} }

View File

@@ -63,27 +63,15 @@ export function makePanelOpenSelector(name) {
); );
} }
export function makePanelHiddenSelector(name) {
return createSelector(
mapSelector,
mapUi => {
const node = utils.getNode(mapUi, name);
return node ? node.isHidden : false;
}
);
}
// interface Map{ // interface Map{
// children: [...{ // children: [...{
// name: (superBlock: String), // name: (superBlock: String),
// isOpen: Boolean, // isOpen: Boolean,
// isHidden: Boolean,
// children: [...{ // children: [...{
// name: (blockName: String), // name: (blockName: String),
// isOpen: Boolean, // isOpen: Boolean,
// isHidden: Boolean,
// children: [...{ // children: [...{
// name: (challengeName: String), // name: (challengeName: String),
// isHidden: Boolean
// }] // }]
// }] // }]
// } // }

View File

@@ -199,7 +199,7 @@ export class FCCNav extends React.Component {
/> />
<img <img
alt='learn to code javascript at freeCodeCamp logo' alt='learn to code javascript at freeCodeCamp logo'
className='img-responsive nav-logo logo-glyph' className='img-responsive logo-glyph'
src={ fCCglyph } src={ fCCglyph }
/> />
</a> </a>

View File

@@ -30,7 +30,7 @@ export default function SignUpButton({ showLoading, showSignUp }) {
key='user' key='user'
> >
<Link to={ onRouteSettings() }> <Link to={ onRouteSettings() }>
My Profile Settings
</Link> </Link>
</li> </li>
); );

View File

@@ -32,6 +32,9 @@
.navbar-brand { .navbar-brand {
padding-top: @navbar-logo-padding; padding-top: @navbar-logo-padding;
padding-bottom: @navbar-logo-padding; padding-bottom: @navbar-logo-padding;
display: flex;
align-items: center;
justify-content: center;
} }
.nav-logo { .nav-logo {
@@ -153,6 +156,28 @@ li.nav-avatar {
} }
.night { .night {
.nav-component-wrapper {
::-webkit-input-placeholder {
color: @night-text-color;
}
::-moz-placeholder {
color: @night-text-color;
}
::-ms-placeholder {
color: @night-text-color;
}
::placeholder {
color: @night-text-color;
}
.fcc_input {
background-color: @night-search-color;
color: @night-text-color;
}
}
.navbar-default { .navbar-default {
.navbar-nav { .navbar-nav {
& > li > a { & > li > a {
@@ -209,31 +234,31 @@ li.nav-avatar {
} }
::-webkit-input-placeholder { ::-webkit-input-placeholder {
color: @brand-primary; color: @input-color-placeholder;
} }
::-moz-placeholder { ::-moz-placeholder {
color: @brand-primary; color: @input-color-placeholder;
} }
::-ms-placeholder { ::-ms-placeholder {
color: @brand-primary; color: @input-color-placeholder;
} }
::placeholder { ::placeholder {
color: @brand-primary; color: @input-color-placeholder;
} }
.navbar-header { .navbar-header {
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
align-items: center; align-items: center;
margin-right: 10px;
} }
} }
.logo-glyph { .logo-glyph {
height: 30px; height: 28px;
width: auto; width: auto;
} }

View File

@@ -25,7 +25,7 @@ export function Divider({ left, dividerClicked }) {
cursor: 'col-resize', cursor: 'col-resize',
height: '100%', height: '100%',
left: left + '%', left: left + '%',
marginLeft: '-4px', marginLeft: '0px',
position: 'absolute', position: 'absolute',
right: 'auto', right: 'auto',
top: 0, top: 0,

View File

@@ -22,7 +22,9 @@ export function Pane({
overflowY: 'auto', overflowY: 'auto',
position: 'absolute', position: 'absolute',
right: right + '%', right: right + '%',
top: 0 top: 0,
paddingLeft: '4px',
paddingRight: '4px'
}; };
return ( return (
<div style={ style }> <div style={ style }>

View File

@@ -1,5 +1,4 @@
import _ from 'lodash'; import _ from 'lodash';
import invariant from 'invariant';
import { import {
composeReducers, composeReducers,
createAction, createAction,
@@ -7,10 +6,11 @@ import {
handleActions handleActions
} from 'berkeleys-redux-utils'; } from 'berkeleys-redux-utils';
import ns from '../ns.json'; import * as utils from './utils.js';
import windowEpic from './window-epic.js'; import windowEpic from './window-epic.js';
import dividerEpic from './divider-epic.js'; import dividerEpic from './divider-epic.js';
import ns from '../ns.json';
import { types as challengeTypes } from '../../routes/Challenges/redux';
export const epics = [ export const epics = [
windowEpic, windowEpic,
@@ -31,8 +31,8 @@ export const types = createTypes([
'windowResized', 'windowResized',
// commands // commands
'updateNavHeight', 'hidePane',
'hidePane' 'updateNavHeight'
], ns); ], ns);
export const panesMapUpdated = createAction( export const panesMapUpdated = createAction(
@@ -51,13 +51,14 @@ export const mouseReleased = createAction(types.mouseReleased);
export const windowResized = createAction(types.windowResized); export const windowResized = createAction(types.windowResized);
// commands // commands
export const updateNavHeight = createAction(types.updateNavHeight);
export const hidePane = createAction(types.hidePane); export const hidePane = createAction(types.hidePane);
export const updateNavHeight = createAction(types.updateNavHeight);
const defaultState = { const defaultState = {
height: 600, height: 600,
width: 800, width: 800,
navHeight: 50, navHeight: 50,
isMapPaneHidden: false,
panes: [], panes: [],
panesByName: {}, panesByName: {},
pressedDivider: null, pressedDivider: null,
@@ -76,56 +77,14 @@ export const pressedDividerSelector =
export const widthSelector = state => getNS(state).width; export const widthSelector = state => getNS(state).width;
export const panesMapSelector = state => getNS(state).panesMap; export const panesMapSelector = state => getNS(state).panesMap;
function isPanesAction({ type } = {}, panesMap) {
return !!panesMap[type];
}
function getDividerLeft(numOfPanes, index) {
let dividerLeft = null;
if (numOfPanes > 1 && numOfPanes !== index + 1) {
dividerLeft = (100 / numOfPanes) * (index + 1);
}
return dividerLeft;
}
function checkForTypeKeys(panesMap) {
_.forEach(panesMap, (_, actionType) => {
invariant(
actionType !== 'undefined',
`action type for ${panesMap[actionType]} is undefined`
);
});
return panesMap;
}
const getPaneName = (panes, index) => (panes[index] || {}).name || '';
function normalizePanesMapCreator(createPanesMap) {
invariant(
_.isFunction(createPanesMap),
'createPanesMap should be a function but got %s',
createPanesMap
);
const panesMap = createPanesMap({}, { type: '@@panes/test' });
if (typeof panesMap === 'function') {
return normalizePanesMapCreator(panesMap);
}
invariant(
!panesMap,
'panesMap test should return undefined or null on test action but got %s',
panesMap
);
return createPanesMap;
}
export default function createPanesAspects({ createPanesMap }) { export default function createPanesAspects({ createPanesMap }) {
createPanesMap = normalizePanesMapCreator(createPanesMap); createPanesMap = utils.normalizePanesMapCreator(createPanesMap);
function middleware({ getState }) { function middleware({ getState }) {
return next => action => { return next => action => {
let finalAction = action; let finalAction = action;
const panesMap = panesMapSelector(getState()); const panesMap = panesMapSelector(getState());
if (isPanesAction(action, panesMap)) { if (utils.isPanesAction(action, panesMap)) {
finalAction = { finalAction = {
...action, ...action,
meta: { meta: {
@@ -138,7 +97,7 @@ export default function createPanesAspects({ createPanesMap }) {
const result = next(finalAction); const result = next(finalAction);
const nextPanesMap = createPanesMap(getState(), action); const nextPanesMap = createPanesMap(getState(), action);
if (nextPanesMap) { if (nextPanesMap) {
checkForTypeKeys(nextPanesMap); utils.checkForTypeKeys(nextPanesMap);
next(panesMapUpdated(action.type, nextPanesMap)); next(panesMapUpdated(action.type, nextPanesMap));
} }
return result; return result;
@@ -154,17 +113,20 @@ export default function createPanesAspects({ createPanesMap }) {
pressedDivider: name pressedDivider: name
}), }),
[types.dividerMoved]: (state, { payload: clientX }) => { [types.dividerMoved]: (state, { payload: clientX }) => {
const { width, pressedDivider: paneName } = state; const {
panes,
panesByName,
pressedDivider: paneName,
width
} = state;
const dividerBuffer = (200 / width) * 100; const dividerBuffer = (200 / width) * 100;
const paneIndex = const paneIndex =
_.findIndex(state.panes, ({ name }) => paneName === name); _.findIndex(state.panes, ({ name }) => paneName === name);
const currentPane = state.panesByName[paneName]; const currentPane = panesByName[paneName];
const rightPane = const rightPane = utils.getPane(panesByName, panes, paneIndex + 1);
state.panesByName[getPaneName(state.panes, paneIndex + 1)] || {}; const leftPane = utils.getPane(panesByName, panes, paneIndex - 1);
const leftPane = const rightBound = utils.getRightBound(rightPane, dividerBuffer);
state.panesByName[getPaneName(state.panes, paneIndex - 1)] || {}; const leftBound = utils.getLeftBound(leftPane, dividerBuffer);
const rightBound = (rightPane.dividerLeft || 100) - dividerBuffer;
const leftBound = (leftPane.dividerLeft || 0) + dividerBuffer;
const newPosition = _.clamp( const newPosition = _.clamp(
(clientX / width) * 100, (clientX / width) * 100,
leftBound, leftBound,
@@ -197,9 +159,13 @@ export default function createPanesAspects({ createPanesMap }) {
[types.updateNavHeight]: (state, { payload: navHeight }) => ({ [types.updateNavHeight]: (state, { payload: navHeight }) => ({
...state, ...state,
navHeight navHeight
}),
[challengeTypes.toggleMap]: state => ({
...state,
isMapPaneHidden: !state.isMapPaneHidden
}) })
}), }),
defaultState, defaultState
), ),
function metaReducer(state = defaultState, action) { function metaReducer(state = defaultState, action) {
if (action.meta && action.meta.panesMap) { if (action.meta && action.meta.panesMap) {
@@ -211,11 +177,11 @@ export default function createPanesAspects({ createPanesMap }) {
panesMap, panesMap,
panes, panes,
panesByName: panes.reduce((panes, { name }, index) => { panesByName: panes.reduce((panes, { name }, index) => {
const dividerLeft = getDividerLeft(numOfPanes, index); const dividerLeft = utils.getDividerLeft(numOfPanes, index);
panes[name] = { panes[name] = {
name, name,
dividerLeft, dividerLeft,
isHidden: false isHidden: name === 'Map' ? state.isMapPaneHidden : false
}; };
return panes; return panes;
}, {}) }, {})
@@ -241,7 +207,7 @@ export default function createPanesAspects({ createPanesMap }) {
panesByName: state.panes.reduce( panesByName: state.panes.reduce(
(panesByName, { name }, index) => { (panesByName, { name }, index) => {
if (!panesByName[name].isHidden) { if (!panesByName[name].isHidden) {
const dividerLeft = getDividerLeft( const dividerLeft = utils.getDividerLeft(
numOfPanes, numOfPanes,
index - numOfHidden index - numOfHidden
); );

View File

@@ -0,0 +1,60 @@
import _ from 'lodash';
import invariant from 'invariant';
export function isPanesAction({ type } = {}, panesMap) {
return !!panesMap[type];
}
export function getDividerLeft(numOfPanes, index) {
let dividerLeft = null;
if (numOfPanes > 1 && numOfPanes !== index + 1) {
dividerLeft = (100 / numOfPanes) * (index + 1);
}
return dividerLeft;
}
export function checkForTypeKeys(panesMap) {
_.forEach(panesMap, (_, actionType) => {
invariant(
actionType !== 'undefined',
`action type for ${panesMap[actionType]} is undefined`
);
});
return panesMap;
}
export const getPane = (panesByName, panes, index) => _.get(
panesByName,
getPaneName(panes, index),
null
);
export const getPaneName = (panes, index) => _.get(
panes,
[index, 'name'],
''
);
export const getRightBound = (pane, buffer) =>
((pane && !pane.isHidden && pane.dividerLeft) || 100) - buffer;
export const getLeftBound = (pane, buffer) =>
((pane && !pane.isHidden && pane.dividerLeft) || 0) + buffer;
export function normalizePanesMapCreator(createPanesMap) {
invariant(
_.isFunction(createPanesMap),
'createPanesMap should be a function but got %s',
createPanesMap
);
const panesMap = createPanesMap({}, { type: '@@panes/test' });
if (typeof panesMap === 'function') {
return normalizePanesMapCreator(panesMap);
}
invariant(
!panesMap,
'panesMap test should return undefined or null on test action but got %s',
panesMap
);
return createPanesMap;
}

View File

@@ -28,7 +28,7 @@ const mapDispatchToProps = function(dispatch) {
const dispatchers = { const dispatchers = {
close: () => dispatch(closeChallengeModal()), close: () => dispatch(closeChallengeModal()),
handleKeypress: (e) => { handleKeypress: (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.meta)) { if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
dispatch(submitChallenge()); dispatch(submitChallenge());
} }
}, },

View File

@@ -0,0 +1,94 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button, Modal } from 'react-bootstrap';
import ns from './ns.json';
import {
createQuestion,
openHelpChatRoom,
closeHelpModal,
helpModalSelector
} from './redux';
import { RSA } from '../../../utils/constantStrings.json';
const mapStateToProps = state => ({ isOpen: helpModalSelector(state) });
const mapDispatchToProps = { createQuestion, openHelpChatRoom, closeHelpModal };
const propTypes = {
closeHelpModal: PropTypes.func,
createQuestion: PropTypes.func,
isOpen: PropTypes.bool,
openHelpChatRoom: PropTypes.func
};
export class HelpModal extends PureComponent {
render() {
const {
isOpen,
closeHelpModal,
openHelpChatRoom,
createQuestion
} = this.props;
return (
<Modal
show={ isOpen }
>
<Modal.Header className={ `${ns}-list-header` }>
Ask for help?
<span
className='close closing-x'
onClick={ closeHelpModal }
>
×
</span>
</Modal.Header>
<Modal.Body className='text-center'>
<h3>
If you've already tried the&nbsp;Read-Search-Ask&nbsp;method,
then you can ask for help on the freeCodeCamp forum.
</h3>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
href={ RSA }
onClick={ closeHelpModal }
target='_blank'
>
Learn about the Read-Search-Ask Methodology
</Button>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
onClick={ createQuestion }
>
Create a help post on the forum
</Button>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
onClick={ openHelpChatRoom }
>
Ask for help in the Gitter Chatroom
</Button>
<Button
block={ true }
bsSize='lg'
bsStyle='primary'
onClick={ closeHelpModal }
>
Cancel
</Button>
</Modal.Body>
</Modal>
);
}
}
HelpModal.displayName = 'HelpModal';
HelpModal.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(HelpModal);

View File

@@ -3,6 +3,8 @@ import { connect } from 'react-redux';
import ns from './ns.json'; import ns from './ns.json';
import { isJSEnabledSelector } from './redux'; import { isJSEnabledSelector } from './redux';
import {Alert} from 'react-bootstrap';
const mainId = 'fcc-main-frame'; const mainId = 'fcc-main-frame';
@@ -23,9 +25,12 @@ export class Preview extends PureComponent {
<div className={ `${ns}-preview` }> <div className={ `${ns}-preview` }>
{ {
!isJSEnabled && ( !isJSEnabled && (
<span className={ `${ns}-preview-js-warning` }> <Alert
bsStyle='info'
className={ `${ns}-preview-js-warning`}
>
JavaScript is disabled. Execute code to enable JavaScript is disabled. Execute code to enable
</span> </Alert>
) )
} }
<iframe <iframe

View File

@@ -15,8 +15,8 @@ import Modern from './views/Modern';
import { import {
fetchChallenge, fetchChallenge,
challengeSelector,
challengeSelector updateTitle
} from '../../redux'; } from '../../redux';
import { makeToast } from '../../Toasts/redux'; import { makeToast } from '../../Toasts/redux';
import { paramsSelector } from '../../Router/redux'; import { paramsSelector } from '../../Router/redux';
@@ -33,7 +33,8 @@ const views = {
const mapDispatchToProps = { const mapDispatchToProps = {
fetchChallenge, fetchChallenge,
makeToast makeToast,
updateTitle
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
@@ -42,12 +43,13 @@ const mapStateToProps = createSelector(
paramsSelector, paramsSelector,
( (
{ dashedName, isTranslated }, { dashedName, isTranslated },
{ viewType }, { viewType, title },
params, params
) => ({ ) => ({
challenge: dashedName, challenge: dashedName,
isTranslated, isTranslated,
params, params,
title,
viewType viewType
}) })
); );
@@ -64,6 +66,8 @@ const propTypes = {
dashedName: PropTypes.string, dashedName: PropTypes.string,
lang: PropTypes.string.isRequired lang: PropTypes.string.isRequired
}), }),
title: PropTypes.string,
updateTitle: PropTypes.func.isRequired,
viewType: PropTypes.string viewType: PropTypes.string
}; };
@@ -82,12 +86,16 @@ export class Show extends PureComponent {
} }
componentDidMount() { componentDidMount() {
this.props.updateTitle(this.props.title);
if (this.isNotTranslated(this.props)) { if (this.isNotTranslated(this.props)) {
this.makeTranslateToast(); this.makeTranslateToast();
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.title !== nextProps.title) {
this.props.updateTitle(nextProps.title);
}
const { params: { dashedName } } = nextProps; const { params: { dashedName } } = nextProps;
if ( if (
this.props.params.dashedName !== dashedName && this.props.params.dashedName !== dashedName &&

View File

@@ -7,6 +7,7 @@ import { connect } from 'react-redux';
import ns from './ns.json'; import ns from './ns.json';
import BugModal from './Bug-Modal.jsx'; import BugModal from './Bug-Modal.jsx';
import HelpModal from './Help-Modal.jsx';
import ToolPanel from './Tool-Panel.jsx'; import ToolPanel from './Tool-Panel.jsx';
import ChallengeTitle from './Challenge-Title.jsx'; import ChallengeTitle from './Challenge-Title.jsx';
import ChallengeDescription from './Challenge-Description.jsx'; import ChallengeDescription from './Challenge-Description.jsx';
@@ -14,6 +15,7 @@ import TestSuite from './Test-Suite.jsx';
import Output from './Output.jsx'; import Output from './Output.jsx';
import { import {
openBugModal, openBugModal,
openHelpModal,
updateHint, updateHint,
executeChallenge, executeChallenge,
unlockUntrustedCode, unlockUntrustedCode,
@@ -22,8 +24,7 @@ import {
testsSelector, testsSelector,
outputSelector, outputSelector,
hintIndexSelector, hintIndexSelector,
codeLockedSelector, codeLockedSelector
chatRoomSelector
} from './redux'; } from './redux';
import { descriptionRegex } from './utils'; import { descriptionRegex } from './utils';
@@ -35,6 +36,7 @@ const mapDispatchToProps = {
executeChallenge, executeChallenge,
updateHint, updateHint,
openBugModal, openBugModal,
openHelpModal,
unlockUntrustedCode unlockUntrustedCode
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
@@ -44,7 +46,6 @@ const mapStateToProps = createSelector(
outputSelector, outputSelector,
hintIndexSelector, hintIndexSelector,
codeLockedSelector, codeLockedSelector,
chatRoomSelector,
( (
{ description }, { description },
{ title }, { title },
@@ -52,24 +53,22 @@ const mapStateToProps = createSelector(
output, output,
hintIndex, hintIndex,
isCodeLocked, isCodeLocked,
helpChatRoom
) => ({ ) => ({
title, title,
description, description,
tests, tests,
output, output,
isCodeLocked, isCodeLocked
helpChatRoom
}) })
); );
const propTypes = { const propTypes = {
description: PropTypes.arrayOf(PropTypes.string), description: PropTypes.arrayOf(PropTypes.string),
executeChallenge: PropTypes.func, executeChallenge: PropTypes.func,
helpChatRoom: PropTypes.string,
hint: PropTypes.string, hint: PropTypes.string,
isCodeLocked: PropTypes.bool, isCodeLocked: PropTypes.bool,
makeToast: PropTypes.func, makeToast: PropTypes.func,
openBugModal: PropTypes.func, openBugModal: PropTypes.func,
openHelpModal: PropTypes.func,
output: PropTypes.string, output: PropTypes.string,
tests: PropTypes.arrayOf(PropTypes.object), tests: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string, title: PropTypes.string,
@@ -125,8 +124,8 @@ export class SidePanel extends PureComponent {
executeChallenge, executeChallenge,
updateHint, updateHint,
makeToast, makeToast,
helpChatRoom,
openBugModal, openBugModal,
openHelpModal,
isCodeLocked, isCodeLocked,
unlockUntrustedCode unlockUntrustedCode
} = this.props; } = this.props;
@@ -147,15 +146,16 @@ export class SidePanel extends PureComponent {
</div> </div>
<ToolPanel <ToolPanel
executeChallenge={ executeChallenge } executeChallenge={ executeChallenge }
helpChatRoom={ helpChatRoom }
hint={ hint } hint={ hint }
isCodeLocked={ isCodeLocked } isCodeLocked={ isCodeLocked }
makeToast={ makeToast } makeToast={ makeToast }
openBugModal={ openBugModal } openBugModal={ openBugModal }
openHelpModal={ openHelpModal }
unlockUntrustedCode={ unlockUntrustedCode } unlockUntrustedCode={ unlockUntrustedCode }
updateHint={ updateHint } updateHint={ updateHint }
/> />
<BugModal /> <BugModal />
<HelpModal />
<Output <Output
defaultOutput={ defaultOutput={
`/** `/**

View File

@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap'; import { Button, Tooltip, OverlayTrigger } from 'react-bootstrap';
const unlockWarning = ( const unlockWarning = (
<Tooltip id='tooltip'> <Tooltip id='tooltip'>
@@ -12,11 +12,11 @@ const unlockWarning = (
const propTypes = { const propTypes = {
executeChallenge: PropTypes.func.isRequired, executeChallenge: PropTypes.func.isRequired,
helpChatRoom: PropTypes.string,
hint: PropTypes.string, hint: PropTypes.string,
isCodeLocked: PropTypes.bool, isCodeLocked: PropTypes.bool,
makeToast: PropTypes.func.isRequired, makeToast: PropTypes.func.isRequired,
openBugModal: PropTypes.func.isRequired, openBugModal: PropTypes.func.isRequired,
openHelpModal: PropTypes.func.isRequired,
unlockUntrustedCode: PropTypes.func.isRequired, unlockUntrustedCode: PropTypes.func.isRequired,
updateHint: PropTypes.func.isRequired updateHint: PropTypes.func.isRequired
}; };
@@ -93,12 +93,13 @@ export default class ToolPanel extends PureComponent {
render() { render() {
const { const {
executeChallenge, executeChallenge,
helpChatRoom,
hint, hint,
isCodeLocked, isCodeLocked,
openBugModal, openBugModal,
openHelpModal,
unlockUntrustedCode unlockUntrustedCode
} = this.props; } = this.props;
return ( return (
<div> <div>
{ this.renderHint(hint, this.makeHint) } { this.renderHint(hint, this.makeHint) }
@@ -110,36 +111,32 @@ export default class ToolPanel extends PureComponent {
) )
} }
<div className='button-spacer' /> <div className='button-spacer' />
<ButtonGroup
className='input-group'
justified={ true }
>
<Button <Button
bsSize='large' block={ true }
bsStyle='primary' bsStyle='primary'
componentClass='label' className='btn-big'
onClick={ this.makeReset } onClick={ this.makeReset }
> >
Reset Reset your code
</Button> </Button>
<div className='button-spacer' />
<Button <Button
bsSize='large' block={ true }
bsStyle='primary' bsStyle='primary'
componentClass='a' className='btn-big'
href={ `https://gitter.im/freecodecamp/${helpChatRoom}` } onClick={ openHelpModal }
target='_blank'
> >
Help Get Help
</Button> </Button>
<div className='button-spacer' />
<Button <Button
bsSize='large' block={ true }
bsStyle='primary' bsStyle='primary'
componentClass='label' className='btn-big'
onClick={ openBugModal } onClick={ openBugModal }
> >
Bug Report a Bug
</Button> </Button>
</ButtonGroup>
<div className='button-spacer' /> <div className='button-spacer' />
</div> </div>
); );

View File

@@ -45,6 +45,10 @@
} }
} }
.@{ns}-instructions-panel {
padding: 0 14px 0 14px;
}
.@{ns}-instructions { .@{ns}-instructions {
margin-bottom: 5px; margin-bottom: 5px;
h4 { h4 {
@@ -94,10 +98,10 @@
color: #02a902; color: #02a902;
} }
.@{ns}-editor .CodeMirror { .@{ns}-editor .CodeMirror {
background-color:#242424; background-color:@night-body-bg;
color:#ABABAB; color:#ABABAB;
&-gutters { &-gutters {
background-color:#242424; background-color:@night-body-bg;
color:#ABABAB; color:#ABABAB;
} }
.cm-bracket, .cm-tag { .cm-bracket, .cm-tag {
@@ -109,6 +113,9 @@
.cm-keyword, .cm-attribute { .cm-keyword, .cm-attribute {
color:#9BBBDC; color:#9BBBDC;
} }
&-line {
caret-color:#ABABAB;
}
} }
.refresh-icon { .refresh-icon {
color: @icon-light-gray; color: @icon-light-gray;
@@ -124,12 +131,12 @@
} }
@keyframes skeletonShimmer{ @keyframes skeletonShimmer{
0% { 0% {
transform: translateX(-48px); transform: translateX(-48px);
} }
100% { 100% {
transform: translateX(1000px); transform: translateX(1000px);
} }
} }
.@{ns}-shimmer { .@{ns}-shimmer {

View File

@@ -19,6 +19,20 @@ import {
const htmlCatch = '\n<!--fcc-->\n'; const htmlCatch = '\n<!--fcc-->\n';
const jsCatch = '\n;/*fcc*/\n'; const jsCatch = '\n;/*fcc*/\n';
const loopProtector = `
window.loopProtect = parent.loopProtect;
window.__err = null;
window.loopProtect.hit = function(line) {
window.__err = new Error(
'Potential infinite loop at line ' +
line +
'. To disable loop protection, write:' +
' // noprotect as the first' +
' line. Beware that if you do have an infinite loop in your code' +
' this will crash your browser.'
);
};
`;
const defaultTemplate = ({ source }) => ` const defaultTemplate = ({ source }) => `
<body style='margin:8px;'> <body style='margin:8px;'>
<!-- fcc-start-source --> <!-- fcc-start-source -->
@@ -28,7 +42,7 @@ const defaultTemplate = ({ source }) => `
`; `;
const wrapInScript = partial(transformContents, (content) => ( const wrapInScript = partial(transformContents, (content) => (
`${htmlCatch}<script>${content}${jsCatch}</script>` `${htmlCatch}<script>${loopProtector}${content}${jsCatch}</script>`
)); ));
const wrapInStyle = partial(transformContents, (content) => ( const wrapInStyle = partial(transformContents, (content) => (
`${htmlCatch}<style>${content}</style>` `${htmlCatch}<style>${content}</style>`

View File

@@ -1,82 +0,0 @@
import { ofType } from 'redux-epic';
import {
types,
closeBugModal
} from '../redux';
import { filesSelector } from '../../../files';
import { currentChallengeSelector } from '../../../redux';
function filesToMarkdown(files = {}) {
const moreThenOneFile = Object.keys(files).length > 1;
return Object.keys(files).reduce((fileString, key) => {
const file = files[key];
if (!file) {
return fileString;
}
const fileName = moreThenOneFile ? `\\ file: ${file.contents}` : '';
const fileType = file.ext;
return fileString +
'\`\`\`' +
fileType +
'\n' +
fileName +
'\n' +
file.contents +
'\n' +
'\`\`\`\n\n';
}, '\n');
}
export default function bugEpic(actions, { getState }, { window }) {
return actions::ofType(types.openIssueSearch, types.createIssue)
.map(({ type }) => {
const state = getState();
const files = filesSelector(state);
const challengeName = currentChallengeSelector(state);
const {
navigator: { userAgent },
location: { href }
} = window;
let titleText = challengeName;
if (type === types.openIssueSearch) {
window.open(
'https://forum.freecodecamp.org/search?q=' +
window.encodeURIComponent(titleText)
);
} else {
titleText = 'Need assistance in ' + challengeName;
let textMessage = [
'#### Challenge Name\n',
'[',
challengeName,
'](',
href,
') has an issue.\n',
'#### Issue Description\n',
'<!-- Describe below when the issue happens and how to ',
'reproduce it -->\n\n\n',
'#### Browser Information\n',
'<!-- Describe your workspace in which you are having issues-->\n',
'User Agent is: <code>',
userAgent,
'</code>.\n\n',
'#### Screenshot\n',
'<!-- Add a screenshot of your issue -->\n\n\n',
'#### Your Code'
].join('');
const body = filesToMarkdown(files);
if (body.length > 10) {
textMessage = textMessage + body;
}
window.open(
'https://forum.freecodecamp.org/new-topic?category=General&title=' +
window.encodeURIComponent(titleText) + '&body=' +
window.encodeURIComponent(textMessage),
'_blank'
);
}
return closeBugModal();
});
}

View File

@@ -10,7 +10,7 @@ import {
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import noop from 'lodash/noop'; import noop from 'lodash/noop';
import bugEpic from './bug-epic'; import modalEpic from './modal-epic';
import completionEpic from './completion-epic.js'; import completionEpic from './completion-epic.js';
import challengeEpic from './challenge-epic.js'; import challengeEpic from './challenge-epic.js';
import executeChallengeEpic from './execute-challenge-epic.js'; import executeChallengeEpic from './execute-challenge-epic.js';
@@ -44,7 +44,7 @@ const challengeToFilesMetaCreator =
_.flow(challengeToFiles, createFilesMetaCreator); _.flow(challengeToFiles, createFilesMetaCreator);
export const epics = [ export const epics = [
bugEpic, modalEpic,
challengeEpic, challengeEpic,
codeStorageEpic, codeStorageEpic,
completionEpic, completionEpic,
@@ -83,6 +83,12 @@ export const types = createTypes([
'openIssueSearch', 'openIssueSearch',
'createIssue', 'createIssue',
// help
'openHelpModal',
'closeHelpModal',
'createQuestion',
'openHelpChatRoom',
// panes // panes
'toggleClassicEditor', 'toggleClassicEditor',
'toggleMain', 'toggleMain',
@@ -157,6 +163,12 @@ export const closeBugModal = createAction(types.closeBugModal);
export const openIssueSearch = createAction(types.openIssueSearch); export const openIssueSearch = createAction(types.openIssueSearch);
export const createIssue = createAction(types.createIssue); export const createIssue = createAction(types.createIssue);
// help
export const openHelpModal = createAction(types.openHelpModal);
export const closeHelpModal = createAction(types.closeHelpModal);
export const createQuestion = createAction(types.createQuestion);
export const openHelpChatRoom = createAction(types.openHelpChatRoom);
// code storage // code storage
export const storedCodeFound = createAction( export const storedCodeFound = createAction(
types.storedCodeFound, types.storedCodeFound,
@@ -174,6 +186,7 @@ const initialUiState = {
output: null, output: null,
isChallengeModalOpen: false, isChallengeModalOpen: false,
isBugOpen: false, isBugOpen: false,
isHelpOpen: false,
successMessage: 'Happy Coding!' successMessage: 'Happy Coding!'
}; };
@@ -206,6 +219,7 @@ export const challengeModalSelector =
state => getNS(state).isChallengeModalOpen; state => getNS(state).isChallengeModalOpen;
export const bugModalSelector = state => getNS(state).isBugOpen; export const bugModalSelector = state => getNS(state).isBugOpen;
export const helpModalSelector = state => getNS(state).isHelpOpen;
export const challengeRequiredSelector = state => export const challengeRequiredSelector = state =>
challengeSelector(state).required || []; challengeSelector(state).required || [];
@@ -318,9 +332,10 @@ export default combineReducers(
...state, ...state,
output: (state.output || '') + output output: (state.output || '') + output
}), }),
[types.openBugModal]: state => ({ ...state, isBugOpen: true }), [types.openBugModal]: state => ({ ...state, isBugOpen: true }),
[types.closeBugModal]: state => ({ ...state, isBugOpen: false }) [types.closeBugModal]: state => ({ ...state, isBugOpen: false }),
[types.openHelpModal]: state => ({ ...state, isHelpOpen: true }),
[types.closeHelpModal]: state => ({ ...state, isHelpOpen: false })
}), }),
initialState, initialState,
ns ns

View File

@@ -0,0 +1,146 @@
import { combineEpics, ofType } from 'redux-epic';
import {
types,
chatRoomSelector,
closeBugModal,
closeHelpModal
} from '../redux';
import { filesSelector } from '../../../files';
import { currentChallengeSelector } from '../../../redux';
function filesToMarkdown(files = {}) {
const moreThenOneFile = Object.keys(files).length > 1;
return Object.keys(files).reduce((fileString, key) => {
const file = files[key];
if (!file) {
return fileString;
}
const fileName = moreThenOneFile ? `\\ file: ${file.contents}` : '';
const fileType = file.ext;
return fileString +
'\`\`\`' +
fileType +
'\n' +
fileName +
'\n' +
file.contents +
'\n' +
'\`\`\`\n\n';
}, '\n');
}
export function openIssueSearchEpic(actions, { getState }, { window }) {
return actions::ofType(types.openIssueSearch).map(() => {
const state = getState();
const challengeName = currentChallengeSelector(state);
window.open(
'https://forum.freecodecamp.org/search?q=' +
window.encodeURIComponent(challengeName)
);
return closeBugModal();
});
}
export function createIssueEpic(actions, { getState }, { window }) {
return actions::ofType(types.createIssue).map(() => {
const state = getState();
const files = filesSelector(state);
const challengeName = currentChallengeSelector(state);
const {
navigator: { userAgent },
location: { href }
} = window;
const titleText = 'Need assistance in ' + challengeName;
let textMessage = [
'#### Challenge Name\n',
'[',
challengeName,
'](',
href,
') has an issue.\n',
'#### Issue Description\n',
'<!-- Describe below when the issue happens and how to ',
'reproduce it -->\n\n\n',
'#### Browser Information\n',
'<!-- Describe your workspace in which you are having issues-->\n',
'User Agent is: <code>',
userAgent,
'</code>.\n\n',
'#### Screenshot\n',
'<!-- Add a screenshot of your issue -->\n\n\n',
'#### Your Code'
].join('');
const body = filesToMarkdown(files);
if (body.length > 10) {
textMessage += body;
}
window.open(
'https://forum.freecodecamp.org/new-topic'
+ '?category=General'
+ '&title=' + window.encodeURIComponent(titleText)
+ '&body=' + window.encodeURIComponent(textMessage),
'_blank'
);
return closeBugModal();
});
}
export function openHelpChatRoomEpic(actions, { getState }, { window }) {
return actions::ofType(types.openHelpChatRoom).map(() => {
const state = getState();
const helpChatRoom = chatRoomSelector(state);
window.open(
'https://gitter.im/freecodecamp/' +
window.encodeURIComponent(helpChatRoom)
);
return closeHelpModal();
});
}
export function createQuestionEpic(actions, { getState }, { window }) {
return actions::ofType(types.createQuestion).map(() => {
const state = getState();
const files = filesSelector(state);
const challengeName = currentChallengeSelector(state);
const {
navigator: { userAgent },
location: { href }
} = window;
const textMessage = [
'**Tell us what\'s happening:**\n\n\n\n',
'**Your code so far**\n',
filesToMarkdown(files),
'**Your browser information:**\n\n',
'User Agent is: <code>',
userAgent,
'</code>.\n\n',
'**Link to the challenge:**\n',
href
].join('');
window.open(
'https://forum.freecodecamp.org/new-topic'
+ '?category=help'
+ '&title=' + window.encodeURIComponent(challengeName)
+ '&body=' + window.encodeURIComponent(textMessage),
'_blank'
);
return closeHelpModal();
});
}
export default combineEpics(
openIssueSearchEpic,
createIssueEpic,
openHelpChatRoomEpic,
createQuestionEpic
);

View File

@@ -11,7 +11,15 @@ const mainId = 'fcc-main-frame';
// the test frame is responsible for running the assert tests // the test frame is responsible for running the assert tests
const testId = 'fcc-test-frame'; const testId = 'fcc-test-frame';
// base tag here will force relative links
// within iframe to point to '/' instead of
// append to the current challenge url
// if an error occurs during initialization
// the __err prop will be set
// This is then picked up in client/frame-runner.js during
// runTestsInTestFrame below
const createHeader = (id = mainId) => ` const createHeader = (id = mainId) => `
<base href='/' target='_blank'/>
<script> <script>
window.__frameId = '${id}'; window.__frameId = '${id}';
window.onerror = function(msg, url, ln, col, err) { window.onerror = function(msg, url, ln, col, err) {

View File

@@ -41,7 +41,7 @@ export class Project extends PureComponent {
const { const {
id, id,
title, title,
image, image = 'ovKSXMs',
isCompleted, isCompleted,
description description
} = this.props; } = this.props;

View File

@@ -108,7 +108,7 @@ export class QuizChallenge extends PureComponent {
<p> <p>
You got {this.props.correct} out of You got {this.props.correct} out of
{this.props.description.length} correct! {` ${this.props.description.length}`} correct!
</p> </p>
{isQuizPassed === false ? ( {isQuizPassed === false ? (

View File

@@ -167,23 +167,24 @@ export class StepChallenge extends PureComponent {
); );
} }
renderStep({ renderStep(
clickOnImage, {
completeAction, clickOnImage,
currentIndex, completeAction,
isActionCompleted, currentIndex,
isLastStep, isActionCompleted,
numOfSteps, isLastStep,
step, numOfSteps,
stepBackward, step,
stepForward stepBackward,
stepForward
}) { }) {
if (!Array.isArray(step)) { if (!Array.isArray(step)) {
return null; return null;
} }
const [imgUrl, imgAlt, info, action] = step; const [imgUrl, imgAlt, info, action] = step;
return ( return (
<div key={ imgUrl }> <div key={ `${info.slice(0, 15)}` }>
<a <a
href={ imgUrl } href={ imgUrl }
onClick={ clickOnImage } onClick={ clickOnImage }
@@ -241,8 +242,8 @@ export class StepChallenge extends PureComponent {
if (!Array.isArray(steps)) { if (!Array.isArray(steps)) {
return null; return null;
} }
return steps.map(([ imgUrl, imgAlt ]) => ( return steps.map(([ imgUrl, imgAlt, info ]) => (
<div key={ imgUrl }> <div key={ `${info.slice(0, 15)}` }>
<Image <Image
alt={ imgAlt } alt={ imgAlt }
responsive={ true } responsive={ true }

View File

@@ -63,10 +63,7 @@ export const actionCompletedSelector = state => getNS(state).isActionCompleted;
export default handleActions( export default handleActions(
() => ({ () => ({
[challenges.challengeUpdated]: () => { [challenges.challengeUpdated]: () => initialState,
console.log('updating step ui');
return initialState;
},
[types.goToStep]: (state, { payload: { step = 0, isUnlocked }}) => ({ [types.goToStep]: (state, { payload: { step = 0, isUnlocked }}) => ({
...state, ...state,
currentIndex: step, currentIndex: step,

View File

@@ -17,7 +17,7 @@ export default function SocialSettings({
}) { }) {
const githubCopy = isGithubCool ? const githubCopy = isGithubCool ?
'Update my profile from GitHub' : 'Update my profile from GitHub' :
'Link my GitHub to unlock my portfolio'; 'Link my GitHub to enable my public profile';
const buttons = [ const buttons = [
<Button <Button
block={ true } block={ true }

View File

@@ -19,11 +19,16 @@ import {
getPort getPort
} from '../../server/utils/url-utils.js'; } from '../../server/utils/url-utils.js';
const debug = debugFactory('fcc:user:remote'); const debug = debugFactory('fcc:models:user');
const BROWNIEPOINTS_TIMEOUT = [1, 'hour']; const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
const createEmailError = () => new Error( const createEmailError = redirectTo => wrapHandledError(
'Please check to make sure the email is a valid email address.' new Error('email format is invalid'),
{
type: 'info',
message: 'Please check to make sure the email is a valid email address.',
redirectTo
}
); );
function destroyAll(id, Model) { function destroyAll(id, Model) {
@@ -76,13 +81,30 @@ function getWaitPeriod(ttl) {
const lastEmailSentAt = moment(new Date(ttl || null)); const lastEmailSentAt = moment(new Date(ttl || null));
const isWaitPeriodOver = ttl ? const isWaitPeriodOver = ttl ?
lastEmailSentAt.isBefore(fiveMinutesAgo) : true; lastEmailSentAt.isBefore(fiveMinutesAgo) : true;
if (!isWaitPeriodOver) { if (!isWaitPeriodOver) {
const minutesLeft = 5 - const minutesLeft = 5 -
(moment().minutes() - lastEmailSentAt.minutes()); (moment().minutes() - lastEmailSentAt.minutes());
return minutesLeft; return minutesLeft;
} }
return 0; return 0;
} }
function getWaitMessage(ttl) {
const minutesLeft = getWaitPeriod(ttl);
if (minutesLeft <= 0) {
return null;
}
const timeToWait = minutesLeft ?
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
'a few seconds';
return dedent`
Please wait ${timeToWait} to resend an authentication link.
`;
}
module.exports = function(User) { module.exports = function(User) {
// set salt factor for passwords // set salt factor for passwords
User.settings.saltWorkFactor = 5; User.settings.saltWorkFactor = 5;
@@ -108,85 +130,87 @@ module.exports = function(User) {
User.findOne$ = Observable.fromNodeCallback(User.findOne, User); User.findOne$ = Observable.fromNodeCallback(User.findOne, User);
User.update$ = Observable.fromNodeCallback(User.updateAll, User); User.update$ = Observable.fromNodeCallback(User.updateAll, User);
User.count$ = Observable.fromNodeCallback(User.count, User); User.count$ = Observable.fromNodeCallback(User.count, User);
User.findOrCreate$ = Observable.fromNodeCallback(User.findOrCreate, User); User.create$ = Observable.fromNodeCallback(
User.create.bind(User)
);
User.prototype.createAccessToken$ = Observable.fromNodeCallback( User.prototype.createAccessToken$ = Observable.fromNodeCallback(
User.prototype.createAccessToken User.prototype.createAccessToken
); );
}); });
User.beforeRemote('create', function({ req }) { User.observe('before save', function(ctx) {
const body = req.body; const beforeCreate = Observable.of(ctx)
// note(berks): we now require all new users to supply an email .filter(({ isNewInstance }) => isNewInstance)
// this was not always the case // User.create
if ( .map(({ instance }) => instance)
typeof body.email !== 'string' || .flatMap(user => {
!isEmail(body.email) // note(berks): we now require all new users to supply an email
) { // this was not always the case
return Promise.reject(createEmailError()); if (
} typeof user.email !== 'string' ||
// assign random username to new users !isEmail(user.email)
// actual usernames will come from github ) {
body.username = 'fcc' + uuid.v4(); throw createEmailError();
if (body) {
// this is workaround for preventing a server crash
// we do this on create and on save
// refer strongloop/loopback/#1364
if (body.password === '') {
body.password = null;
}
// set email verified false on user email signup
// should not be set with oauth signin methods
body.emailVerified = false;
}
return User.doesExist(null, body.email)
.catch(err => {
throw wrapHandledError(err, { redirectTo: '/email-signup' });
})
.then(exists => {
if (!exists) {
return null;
} }
const err = wrapHandledError( // assign random username to new users
new Error('user already exists'), // actual usernames will come from github
{ // use full uuid to ensure uniqueness
redirectTo: '/email-signin', user.username = 'fcc' + uuid.v4();
message: dedent`
The ${body.email} email address is already associated with an account.
Try signing in with it here instead.
`
}
);
throw err;
});
});
User.observe('before save', function({ instance: user }, next) { if (!user.progressTimestamps) {
if (user) { user.progressTimestamps = [];
// Some old accounts will not have emails associated with theme }
// we verify only if the email field is populated
if (user.email && !isEmail(user.email)) {
return next(createEmailError());
}
user.username = user.username.trim().toLowerCase();
user.email = typeof user.email === 'string' ?
user.email.trim().toLowerCase() :
user.email;
if (!user.progressTimestamps) { if (user.progressTimestamps.length === 0) {
user.progressTimestamps = []; user.progressTimestamps.push({ timestamp: Date.now() });
} }
return Observable.fromPromise(User.doesExist(null, user.email))
.do(exists => {
if (exists) {
throw wrapHandledError(
new Error('user already exists'),
{
redirectTo: '/email-signin',
message: dedent`
The ${user.email} email address is already associated with an account.
Try signing in with it here instead.
`
}
);
}
});
})
.ignoreElements();
if (user.progressTimestamps.length === 0) { const updateOrSave = Observable.of(ctx)
user.progressTimestamps.push({ timestamp: Date.now() }); // not new
} .filter(({ isNewInstance }) => !isNewInstance)
// this is workaround for preventing a server crash .map(({ instance }) => instance)
// we do this on save and on create // is update or save user
// refer strongloop/loopback/#1364 .filter(Boolean)
if (user.password === '') { .do(user => {
user.password = null; // Some old accounts will not have emails associated with theme
} // we verify only if the email field is populated
} if (user.email && !isEmail(user.email)) {
return next(); throw createEmailError();
}
user.username = user.username.trim().toLowerCase();
user.email = typeof user.email === 'string' ?
user.email.trim().toLowerCase() :
user.email;
if (!user.progressTimestamps) {
user.progressTimestamps = [];
}
if (user.progressTimestamps.length === 0) {
user.progressTimestamps.push({ timestamp: Date.now() });
}
})
.ignoreElements();
return Observable.merge(beforeCreate, updateOrSave)
.toPromise();
}); });
// remove lingering user identities before deleting user // remove lingering user identities before deleting user
@@ -224,163 +248,67 @@ module.exports = function(User) {
}); });
debug('setting up user hooks'); debug('setting up user hooks');
// overwrite lb confirm
User.beforeRemote('confirm', function(ctx, _, next) { User.confirm = function(uid, token, redirectTo) {
return this.findById(uid)
if (!ctx.req.query) { .then(user => {
return ctx.res.redirect('/'); if (!user) {
} throw wrapHandledError(
new Error(`User not found: ${uid}`),
const uid = ctx.req.query.uid; {
const token = ctx.req.query.token; // standard oops
const redirect = ctx.req.query.redirect; type: 'info',
redirectTo
return User.findById(uid, (err, user) => { }
);
if (err || !user || !user.newEmail) {
ctx.req.flash('danger', {
msg: dedent`Oops, something went wrong, please try again later`
});
return ctx.res.redirect('/');
} }
if (user.verificationToken !== token) {
if (!user.verificationToken && !user.emailVerified) { throw wrapHandledError(
ctx.req.flash('info', { new Error(`Invalid token: ${token}`),
msg: dedent`Looks like we have your email. But you haven't {
verified it yet, please sign in and request a fresh verification type: 'info',
link.` message: dedent`
}); Looks like you have clicked an invalid link.
return ctx.res.redirect(redirect); Please sign in and request a fresh one.
`,
redirectTo
}
);
} }
if (!user.verificationToken && user.emailVerified) {
ctx.req.flash('info', {
msg: dedent`Looks like you have already verified your email.
Please sign in to continue.`
});
return ctx.res.redirect(redirect);
}
if (user.verificationToken && user.verificationToken !== token) {
ctx.req.flash('info', {
msg: dedent`Looks like you have clicked an invalid link.
Please sign in and request a fresh one.`
});
return ctx.res.redirect(redirect);
}
return user.update$({ return user.update$({
email: user.newEmail, email: user.newEmail,
emailVerified: true,
emailVerifyTTL: null,
newEmail: null, newEmail: null,
emailVerifyTTL: null verificationToken: null
}) }).toPromise();
.do(() => {
return next();
})
.toPromise();
});
});
User.afterRemote('confirm', function(ctx) {
if (!ctx.req.query) {
return ctx.res.redirect('/');
}
const redirect = ctx.req.query.redirect;
ctx.req.flash('success', {
msg: [
'Your email has been confirmed!'
]
});
return ctx.res.redirect(redirect);
});
User.beforeRemote('create', function({ req, res }, _, next) {
req.body.username = 'fcc' + uuid.v4().slice(0, 8);
if (!req.body.email) {
return next();
}
if (!isEmail(req.body.email)) {
return next(new Error('Email format is not valid'));
}
return User.doesExist(null, req.body.email)
.then(exists => {
if (!exists) {
return next();
}
req.flash('danger', {
msg: dedent`
The ${req.body.email} email address is already associated with an account.
Try signing in with it here instead.
`
});
return res.redirect('/email-signin');
})
.catch(err => {
console.error(err);
req.flash('danger', {
msg: 'Oops, something went wrong, please try again later'
});
return res.redirect('/email-signin');
}); });
}); };
User.beforeRemote('login', function(ctx, notUsed, next) { User.prototype.loginByRequest = function login(req, res) {
const { body } = ctx.req; const createToken = this.createAccessToken$()
if (body && typeof body.email === 'string') { .do(accessToken => {
if (!isEmail(body.email)) { const config = {
return next(createEmailError()); signed: !!req.signedCookies,
} maxAge: accessToken.ttl
body.email = body.email.toLowerCase(); };
} if (accessToken && accessToken.id) {
return next(); res.cookie('access_token', accessToken.id, config);
}); res.cookie('userId', accessToken.userId, config);
User.afterRemote('login', function(ctx, accessToken, next) {
var res = ctx.res;
var req = ctx.req;
// var args = ctx.args;
var config = {
signed: !!req.signedCookies,
maxAge: accessToken.ttl
};
if (accessToken && accessToken.id) {
debug('setting cookies');
res.cookie('access_token', accessToken.id, config);
res.cookie('userId', accessToken.userId, config);
}
return req.logIn({ id: accessToken.userId.toString() }, function(err) {
if (err) { return next(err); }
debug('user logged in');
if (req.session && req.session.returnTo) {
var redirectTo = req.session.returnTo;
if (redirectTo === '/map-aside') {
redirectTo = '/map';
} }
return res.redirect(redirectTo); });
} const updateUser = this.update$({
emailVerified: true,
req.flash('success', { msg: 'Success! You are now logged in.' }); emailAuthLinkTTL: null,
return res.redirect('/'); emailVerifyTTL: null
}); });
}); return Observable.combineLatest(
createToken,
User.afterRemoteError('login', function(ctx) { updateUser,
var res = ctx.res; req.logIn(this),
var req = ctx.req; (accessToken) => accessToken,
);
req.flash('danger', { };
msg: 'Invalid username or password.'
});
return res.redirect('/email-signin');
});
User.afterRemote('logout', function(ctx, result, next) { User.afterRemote('logout', function(ctx, result, next) {
var res = ctx.res; var res = ctx.res;
@@ -482,142 +410,141 @@ module.exports = function(User) {
} }
); );
User.requestAuthEmail = function requestAuthEmail(email) { User.prototype.createAuthToken = function createAuthToken({ ttl } = {}) {
if (!isEmail(email)) { return Observable.fromNodeCallback(
return Promise.reject( this.authTokens.create.bind(this.authTokens)
new Error('The submitted email not valid.') )({ ttl });
);
}
var userObj = {
username: 'fcc' + uuid.v4().slice(0, 8),
email: email,
emailVerified: false
};
return User.findOrCreate$({ where: { email }}, userObj)
.flatMap(([ user, isCreated ]) => {
const minutesLeft = getWaitPeriod(user.emailAuthLinkTTL);
if (minutesLeft > 0) {
const timeToWait = minutesLeft ?
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
'a few seconds';
debug('request before wait time : ' + timeToWait);
return Observable.of(dedent`
Please wait ${timeToWait} to resend an authentication link.
`);
}
const renderAuthEmail = isCreated ?
renderSignUpEmail : renderSignInEmail;
// create a temporary access token with ttl for 15 minutes
return user.createAccessToken$({ ttl: 15 * 60 * 1000 })
.flatMap(token => {
const { id: loginToken } = token;
const loginEmail = new Buffer(user.email).toString('base64');
const host = getServerFullURL();
const mailOptions = {
type: 'email',
to: user.email,
from: getEmailSender(),
subject: 'freeCodeCamp - Authentication Request!',
text: renderAuthEmail({
host,
loginEmail,
loginToken
})
};
return this.email.send$(mailOptions)
.flatMap(() => {
const emailAuthLinkTTL = token.created;
return this.update$({
emailAuthLinkTTL
})
.map(() => {
return dedent`
If you entered a valid email, a magic link is on its way.
Please follow that link to sign in.
`;
});
});
});
})
.catch(err => {
if (err) { debug(err); }
return dedent`
Oops, something is not right, please try again later.
`;
})
.toPromise();
}; };
User.remoteMethod( User.prototype.getEncodedEmail = function getEncodedEmail() {
'requestAuthEmail', if (!this.email) {
{ return null;
description: 'request a link on email with temporary token to sign in', }
accepts: [{ return Buffer(this.email).toString('base64');
arg: 'email', type: 'string', required: true };
}],
returns: [{ User.decodeEmail = email => Buffer(email, 'base64').toString();
arg: 'message', type: 'string'
}], User.prototype.requestAuthEmail = function requestAuthEmail(isSignUp) {
http: { return Observable.defer(() => {
path: '/request-auth-link', verb: 'POST' const messageOrNull = getWaitMessage(this.emailAuthLinkTTL);
if (messageOrNull) {
throw wrapHandledError(
new Error('request is throttled'),
{
type: 'info',
message: messageOrNull
}
);
} }
}
);
User.prototype.requestUpdateEmail = function requestUpdateEmail( // create a temporary access token with ttl for 15 minutes
newEmail return this.createAuthToken({ ttl: 15 * 60 * 1000 });
) { })
const ownEmail = newEmail === this.email; .flatMap(token => {
if (!isEmail('' + newEmail)) { let renderAuthEmail = renderSignInEmail;
debug('invalid email:', newEmail ); let subject = 'Login Requested - freeCodeCamp';
return Observable.throw(createEmailError()); if (isSignUp) {
} renderAuthEmail = renderSignUpEmail;
// email is already associated and verified with this account subject = 'Account Created - freeCodeCamp';
if (ownEmail && this.emailVerified) {
return Observable.throw(new Error(
`${newEmail} is already associated with this account.`
));
}
const minutesLeft = getWaitPeriod(this.emailVerifyTTL);
if (ownEmail && minutesLeft > 0) {
const timeToWait = minutesLeft ?
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
'a few seconds';
debug('request before wait time : ' + timeToWait);
return Observable.of(dedent`
Please wait ${timeToWait} to resend an authentication link.
`);
}
return Observable.fromPromise(User.doesExist(null, newEmail))
.flatMap(exists => {
// not associated with this account, but is associated with another
if (!ownEmail && exists) {
return Promise.reject(
new Error(
`${newEmail} is already associated with another account.`
)
);
} }
const { id: loginToken, created: emailAuthLinkTTL } = token;
const loginEmail = this.getEncodedEmail();
const host = getServerFullURL();
const mailOptions = {
type: 'email',
to: this.email,
from: getEmailSender(),
subject,
text: renderAuthEmail({
host,
loginEmail,
loginToken
})
};
return Observable.combineLatest(
User.email.send$(mailOptions),
this.update$({ emailAuthLinkTTL })
);
})
.map(() => isSignUp ?
dedent`
We've created a new account for you.
If you entered a valid email, a magic link is on its way.
Please follow that link to sign in.
` :
dedent`
If you entered a valid email, a magic link is on its way.
Please follow that link to sign in.
`
);
};
User.prototype.requestUpdateEmail = function requestUpdateEmail(newEmail) {
return Observable.defer(() => {
const ownEmail = newEmail === this.email;
if (!isEmail('' + newEmail)) {
throw createEmailError();
}
// email is already associated and verified with this account
if (ownEmail) {
if (this.emailVerified) {
throw wrapHandledError(
new Error('email is already verified'),
{
type: 'info',
message: `${newEmail} is already associated with this account.`
}
);
} else {
const messageOrNull = getWaitMessage(this.emailVerifyTTL);
// email is already associated but unverified
if (messageOrNull) {
// email is within time limit
throw wrapHandledError(
new Error(),
{
type: 'info',
message: messageOrNull
}
);
}
}
}
// at this point email is not associated with the account
// or has not been verified but user is requesting another token
// outside of the time limit
return Observable.if(
() => ownEmail,
Observable.empty(),
// defer prevents the promise from firing prematurely (before subscribe)
Observable.defer(() => User.doesExist(null, newEmail))
)
.do(exists => {
// not associated with this account, but is associated with another
if (exists) {
throw wrapHandledError(
new Error('email already in use'),
{
type: 'info',
message:
`${newEmail} is already associated with another account.`
}
);
}
})
.defaultIfEmpty();
})
.flatMap(() => {
const emailVerified = false; const emailVerified = false;
return this.update$({ const data = {
newEmail, newEmail,
emailVerified, emailVerified,
emailVerifyTTL: new Date() emailVerifyTTL: new Date()
}) };
.do(() => { return this.update$(data).do(() => Object.assign(this, data));
this.newEmail = newEmail;
this.emailVerified = emailVerified;
this.emailVerifyTTL = new Date();
});
}) })
.flatMap(() => { .flatMap(() => {
const mailOptions = { const mailOptions = {

View File

@@ -16,12 +16,16 @@
} }
} }
}, },
"newEmail":{ "newEmail": {
"type": "string" "type": "string"
}, },
"emailVerifyTTL": { "emailVerifyTTL": {
"type": "date" "type": "date"
}, },
"emailVerified": {
"type": "boolean",
"default": false
},
"emailAuthLinkTTL": { "emailAuthLinkTTL": {
"type": "date" "type": "date"
}, },
@@ -179,6 +183,36 @@
"description": "Campers is full stack certified", "description": "Campers is full stack certified",
"default": false "default": false
}, },
"isRespWebDesignCert": {
"type": "boolean",
"description": "Camper is data visualization certified",
"default": false
},
"isNewDataVisCert": {
"type": "boolean",
"description": "Camper is responsive web design certified",
"default": false
},
"isFrontEndLibsCert": {
"type": "boolean",
"description": "Camper is front end libraries certified",
"default": false
},
"isJsAlgoDataStructCert": {
"type": "boolean",
"description": "Camper is javascript algorithms and data structures certified",
"default": false
},
"isApisMicroservicesCert": {
"type": "boolean",
"description": "Camper is apis and microservices certified",
"default": false
},
"isInfosecQaCert": {
"type": "boolean",
"description": "Camper is information security and quality assurance certified",
"default": false
},
"isChallengeMapMigrated": { "isChallengeMapMigrated": {
"type": "boolean", "type": "boolean",
"description": "Migrate completedChallenges array to challenge map", "description": "Migrate completedChallenges array to challenge map",
@@ -254,6 +288,14 @@
"type": "hasOne", "type": "hasOne",
"model": "pledge", "model": "pledge",
"foreignKey": "" "foreignKey": ""
},
"authTokens": {
"type": "hasMany",
"model": "AuthToken",
"foreignKey": "userId",
"options": {
"disableInclude": true
}
} }
}, },
"acls": [ "acls": [
@@ -263,6 +305,32 @@
"principalId": "$everyone", "principalId": "$everyone",
"permission": "DENY" "permission": "DENY"
}, },
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"property": "create"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"property": "login"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"property": "verify"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY",
"property": "resetPassword"
},
{ {
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",
@@ -284,13 +352,6 @@
"permission": "ALLOW", "permission": "ALLOW",
"property": "giveBrowniePoints" "property": "giveBrowniePoints"
}, },
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "requestUpdateEmail"
},
{ {
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",
@@ -304,13 +365,6 @@
"principalId": "$owner", "principalId": "$owner",
"permission": "ALLOW", "permission": "ALLOW",
"property": "updateLanguage" "property": "updateLanguage"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "requestAuthEmail"
} }
], ],
"methods": {} "methods": {}

View File

@@ -3,5 +3,6 @@
"defaultProfileImage": "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png", "defaultProfileImage": "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png",
"donateUrl": "https://www.freecodecamp.org/donate", "donateUrl": "https://www.freecodecamp.org/donate",
"forumUrl": "https://forum.freecodecamp.org", "forumUrl": "https://forum.freecodecamp.org",
"githubUrl": "https://github.com/freecodecamp/freecodecamp" "githubUrl": "https://github.com/freecodecamp/freecodecamp",
"RSA": "https://forum.freecodecamp.org/t/the-read-search-ask-methodology-for-getting-unstuck/137307"
} }

View File

@@ -244,7 +244,7 @@ gulp.task('dev-server', syncDepenedents, function() {
host: `${hostname}:${syncPort}` host: `${hostname}:${syncPort}`
}) })
}, },
logLeval: 'debug', logLevel: 'info',
files: paths.syncWatch, files: paths.syncWatch,
port: syncPort, port: syncPort,
open: false, open: false,

303
package-lock.json generated
View File

@@ -78,15 +78,10 @@
} }
} }
}, },
"@types/bluebird": {
"version": "3.5.18",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.18.tgz",
"integrity": "sha512-OTPWHmsyW18BhrnG5x8F7PzeZ2nFxmHGb42bZn79P9hl+GI5cMzyPgQTwNjbem0lJhoru/8vtjAFCUOu3+gE2w=="
},
"@types/body-parser": { "@types/body-parser": {
"version": "1.16.7", "version": "1.16.8",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.7.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.8.tgz",
"integrity": "sha512-Obn1/GG0sYsnlAlhhSR1hvYRGBpQT+fzSi2IlGN8emCE4iu6f6xIjaq499B1sa7N9iBLzxyOUBo5bzgJd16BvA==", "integrity": "sha512-BdN2PXxOFnTXFcyONPW6t0fHjz2fvRZHVMFpaS0wYr+Y8fWEaNOs4V8LEu/fpzQlMx+ahdndgTaGTwPC+J/EeA==",
"requires": { "requires": {
"@types/express": "4.0.39", "@types/express": "4.0.39",
"@types/node": "8.0.47" "@types/node": "8.0.47"
@@ -97,15 +92,15 @@
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.39.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.39.tgz",
"integrity": "sha512-dBUam7jEjyuEofigUXCtublUHknRZvcRgITlGsTbFgPvnTwtQUt2NgLakbsf+PsGo/Nupqr3IXCYsOpBpofyrA==", "integrity": "sha512-dBUam7jEjyuEofigUXCtublUHknRZvcRgITlGsTbFgPvnTwtQUt2NgLakbsf+PsGo/Nupqr3IXCYsOpBpofyrA==",
"requires": { "requires": {
"@types/body-parser": "1.16.7", "@types/body-parser": "1.16.8",
"@types/express-serve-static-core": "4.0.56", "@types/express-serve-static-core": "4.11.0",
"@types/serve-static": "1.13.0" "@types/serve-static": "1.13.1"
} }
}, },
"@types/express-serve-static-core": { "@types/express-serve-static-core": {
"version": "4.0.56", "version": "4.11.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.56.tgz", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.11.0.tgz",
"integrity": "sha512-/0nwIzF1Bd4KGwW4lhDZYi5StmCZG1DIXXMfQ/zjORzlm4+F1eRA4c6yJQrt4hqX//TDtPULpSlYwmSNyCMeMg==", "integrity": "sha512-hOi1QNb+4G+UjDt6CEJ6MjXHy+XceY7AxIa28U9HgJ80C+3gIbj7h5dJNxOI7PU3DO1LIhGP5Bs47Dbf5l8+MA==",
"requires": { "requires": {
"@types/node": "8.0.47" "@types/node": "8.0.47"
} }
@@ -121,11 +116,11 @@
"integrity": "sha512-kOwL746WVvt/9Phf6/JgX/bsGQvbrK5iUgzyfwZNcKVFcjAUVSpF9HxevLTld2SG9aywYHOILj38arDdY1r/iQ==" "integrity": "sha512-kOwL746WVvt/9Phf6/JgX/bsGQvbrK5iUgzyfwZNcKVFcjAUVSpF9HxevLTld2SG9aywYHOILj38arDdY1r/iQ=="
}, },
"@types/serve-static": { "@types/serve-static": {
"version": "1.13.0", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.0.tgz", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz",
"integrity": "sha512-wvQkePwCDZoyQPGb64DTl2TEeLw54CQFXjY+tznxYYxNcBb4LG40ezoVbMDa0epwE4yogB0f42jCaH0356x5Mg==", "integrity": "sha512-jDMH+3BQPtvqZVIcsH700Dfi8Q3MIcEx16g/VdxjoqiGR/NntekB10xdBpirMKnPe9z2C5cBmL0vte0YttOr3Q==",
"requires": { "requires": {
"@types/express-serve-static-core": "4.0.56", "@types/express-serve-static-core": "4.11.0",
"@types/mime": "2.0.0" "@types/mime": "2.0.0"
} }
}, },
@@ -511,6 +506,12 @@
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
"integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=" "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw="
}, },
"ast-types": {
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz",
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
"dev": true
},
"async": { "async": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz",
@@ -1535,6 +1536,18 @@
"babel-runtime": "6.26.0" "babel-runtime": "6.26.0"
} }
}, },
"babel-plugin-transform-imports": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-imports/-/babel-plugin-transform-imports-1.4.1.tgz",
"integrity": "sha512-o7EqCZFj0pKUUDwYDZLTRSg1wNMN69p31l3Sf1+ujTFjWUq+/plAUJ04kO1kn5oLVaHbwLi2F4jQbIHFTN2t+A==",
"dev": true,
"requires": {
"babel-types": "6.26.0",
"lodash.camelcase": "4.3.0",
"lodash.kebabcase": "4.1.1",
"lodash.snakecase": "4.1.1"
}
},
"babel-plugin-transform-object-rest-spread": { "babel-plugin-transform-object-rest-spread": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz",
@@ -1828,6 +1841,12 @@
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=", "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=",
"dev": true "dev": true
}, },
"base62": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/base62/-/base62-1.2.1.tgz",
"integrity": "sha512-xVtfFHNPUzpCNHygpXFGMlDk3saxXLQcOOQzAAk6ibvlAHgT6WKXLv9rMFhcyEK1n9LuDmp/LxyGW/Fm9L8++g==",
"dev": true
},
"base64-arraybuffer": { "base64-arraybuffer": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
@@ -3072,6 +3091,38 @@
"resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz", "resolved": "https://registry.npmjs.org/commondir/-/commondir-0.0.1.tgz",
"integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I=" "integrity": "sha1-ifAP3NUbUZxXhzP+xWPmptp/W+I="
}, },
"commoner": {
"version": "0.10.8",
"resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz",
"integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=",
"dev": true,
"requires": {
"commander": "2.6.0",
"detective": "4.7.1",
"glob": "5.0.15",
"graceful-fs": "4.1.11",
"iconv-lite": "0.4.19",
"mkdirp": "0.5.1",
"private": "0.1.8",
"q": "1.5.1",
"recast": "0.11.23"
},
"dependencies": {
"glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
"dev": true,
"requires": {
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
}
}
},
"component-bind": { "component-bind": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
@@ -3954,6 +4005,24 @@
"integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
"dev": true "dev": true
}, },
"detective": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz",
"integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==",
"dev": true,
"requires": {
"acorn": "5.3.0",
"defined": "1.0.0"
},
"dependencies": {
"acorn": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz",
"integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==",
"dev": true
}
}
},
"dev-ip": { "dev-ip": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz",
@@ -4356,6 +4425,16 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
}, },
"envify": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/envify/-/envify-3.4.1.tgz",
"integrity": "sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg=",
"dev": true,
"requires": {
"jstransform": "11.0.3",
"through": "2.3.8"
}
},
"enzyme": { "enzyme": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.2.0.tgz", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.2.0.tgz",
@@ -4418,15 +4497,6 @@
"is-arrayish": "0.2.1" "is-arrayish": "0.2.1"
} }
}, },
"errorhandler": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz",
"integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=",
"requires": {
"accepts": "1.3.4",
"escape-html": "1.0.3"
}
},
"es-abstract": { "es-abstract": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz",
@@ -5207,22 +5277,13 @@
} }
}, },
"express-validator": { "express-validator": {
"version": "3.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-3.2.1.tgz", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-4.3.0.tgz",
"integrity": "sha1-RWA+fu5pMYXCGY+969QUkl/9NSQ=", "integrity": "sha512-EYU+JJ2EoLpcw+GKwbB1K8UGb/w1A70Wf3gD/zE9QScQxeSt8qad93lxGtsLwZFoiYM0EByVoSzHJnskp+eVHQ==",
"requires": { "requires": {
"@types/bluebird": "3.5.18",
"@types/express": "4.0.39", "@types/express": "4.0.39",
"bluebird": "3.5.1",
"lodash": "4.17.4", "lodash": "4.17.4",
"validator": "6.2.1" "validator": "8.2.0"
},
"dependencies": {
"validator": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.2.1.tgz",
"integrity": "sha1-vFdbeNFb6y4zimZbqVMMf0Ce9mc="
}
} }
}, },
"extend": { "extend": {
@@ -9076,6 +9137,42 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"jstransform": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz",
"integrity": "sha1-CaeJk+CuTU70SH9hVakfYZDLQiM=",
"dev": true,
"requires": {
"base62": "1.2.1",
"commoner": "0.10.8",
"esprima-fb": "15001.1.0-dev-harmony-fb",
"object-assign": "2.1.1",
"source-map": "0.4.4"
},
"dependencies": {
"esprima-fb": {
"version": "15001.1.0-dev-harmony-fb",
"resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz",
"integrity": "sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE=",
"dev": true
},
"object-assign": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
"integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=",
"dev": true
},
"source-map": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true,
"requires": {
"amdefine": "1.0.1"
}
}
}
},
"jstransformer": { "jstransformer": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-0.0.2.tgz",
@@ -10044,6 +10141,12 @@
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
}, },
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
"lodash.clone": { "lodash.clone": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
@@ -10119,6 +10222,12 @@
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
"dev": true "dev": true
}, },
"lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
"integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=",
"dev": true
},
"lodash.keys": { "lodash.keys": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
@@ -10175,6 +10284,12 @@
"integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
"dev": true "dev": true
}, },
"lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=",
"dev": true
},
"lodash.some": { "lodash.some": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
@@ -12749,9 +12864,9 @@
"dev": true "dev": true
}, },
"passport": { "passport": {
"version": "0.2.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.2.2.tgz", "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz",
"integrity": "sha1-nDjxe+uSnz2Br3uIOOhDDbhwPys=", "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=",
"requires": { "requires": {
"passport-strategy": "1.0.0", "passport-strategy": "1.0.0",
"pause": "0.0.1" "pause": "0.0.1"
@@ -13161,6 +13276,12 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
}, },
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
"dev": true
},
"qs": { "qs": {
"version": "6.5.1", "version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
@@ -13617,6 +13738,26 @@
} }
} }
}, },
"recast": {
"version": "0.11.23",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz",
"integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=",
"dev": true,
"requires": {
"ast-types": "0.9.6",
"esprima": "3.1.3",
"private": "0.1.8",
"source-map": "0.5.7"
},
"dependencies": {
"esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
"dev": true
}
}
},
"rechoir": { "rechoir": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
@@ -16959,9 +17100,9 @@
} }
}, },
"validator": { "validator": {
"version": "6.3.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz",
"integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g=" "integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA=="
}, },
"value-equal": { "value-equal": {
"version": "0.4.0", "version": "0.4.0",
@@ -17408,6 +17549,76 @@
} }
} }
}, },
"webpack-visualizer-plugin": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/webpack-visualizer-plugin/-/webpack-visualizer-plugin-0.1.11.tgz",
"integrity": "sha1-uHcK2GtPZSYSxosbeCJT+vn4o04=",
"dev": true,
"requires": {
"d3": "3.5.17",
"mkdirp": "0.5.1",
"react": "0.14.9",
"react-dom": "0.14.9"
},
"dependencies": {
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
"dev": true
},
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
"dev": true
},
"fbjs": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.1.tgz",
"integrity": "sha1-lja3cF9bqWhNRLcveDISVK/IYPc=",
"dev": true,
"requires": {
"core-js": "1.2.7",
"loose-envify": "1.3.1",
"promise": "7.3.1",
"ua-parser-js": "0.7.17",
"whatwg-fetch": "0.9.0"
}
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"dev": true,
"requires": {
"asap": "2.0.6"
}
},
"react": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz",
"integrity": "sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE=",
"dev": true,
"requires": {
"envify": "3.4.1",
"fbjs": "0.6.1"
}
},
"react-dom": {
"version": "0.14.9",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-0.14.9.tgz",
"integrity": "sha1-BQZKPc8PsYgKOyv8nVjFXY2fYpM=",
"dev": true
},
"whatwg-fetch": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz",
"integrity": "sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA=",
"dev": true
}
}
},
"weinre": { "weinre": {
"version": "2.0.0-pre-I0Z7U9OV", "version": "2.0.0-pre-I0Z7U9OV",
"resolved": "https://registry.npmjs.org/weinre/-/weinre-2.0.0-pre-I0Z7U9OV.tgz", "resolved": "https://registry.npmjs.org/weinre/-/weinre-2.0.0-pre-I0Z7U9OV.tgz",

View File

@@ -58,13 +58,12 @@
"emmet-codemirror": "^1.2.5", "emmet-codemirror": "^1.2.5",
"enzyme": "^3.2.0", "enzyme": "^3.2.0",
"enzyme-adapter-react-15": "^1.0.5", "enzyme-adapter-react-15": "^1.0.5",
"errorhandler": "^1.4.2",
"es6-map": "~0.1.1", "es6-map": "~0.1.1",
"express": "^4.13.3", "express": "^4.13.3",
"express-flash": "~0.0.2", "express-flash": "~0.0.2",
"express-session": "^1.12.1", "express-session": "^1.12.1",
"express-state": "^1.2.0", "express-state": "^1.2.0",
"express-validator": "^3.0.0", "express-validator": "^4.3.0",
"fetchr": "~0.5.12", "fetchr": "~0.5.12",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"frameguard": "^3.0.0", "frameguard": "^3.0.0",
@@ -96,7 +95,7 @@
"normalizr": "2.2.1", "normalizr": "2.2.1",
"object.assign": "^4.0.3", "object.assign": "^4.0.3",
"opbeat": "^4.14.0", "opbeat": "^4.14.0",
"passport": "^0.2.1", "passport": "^0.4.0",
"passport-facebook": "^2.0.0", "passport-facebook": "^2.0.0",
"passport-github": "^1.0.0", "passport-github": "^1.0.0",
"passport-google-oauth2": "~0.1.6", "passport-google-oauth2": "~0.1.6",
@@ -135,7 +134,7 @@
"snyk": "^1.30.1", "snyk": "^1.30.1",
"store": "git+https://github.com/berkeleytrue/store.js.git#feature/noop-server", "store": "git+https://github.com/berkeleytrue/store.js.git#feature/noop-server",
"uuid": "^3.0.1", "uuid": "^3.0.1",
"validator": "^6.0.0" "validator": "^8.2.0"
}, },
"devDependencies": { "devDependencies": {
"adler32": "~0.1.7", "adler32": "~0.1.7",
@@ -144,6 +143,7 @@
"babel-loader": "^6.2.1", "babel-loader": "^6.2.1",
"babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-lodash": "^3.2.11", "babel-plugin-lodash": "^3.2.11",
"babel-plugin-transform-imports": "^1.4.1",
"babel-preset-stage-0": "^6.3.13", "babel-preset-stage-0": "^6.3.13",
"browser-sync": "^2.9.12", "browser-sync": "^2.9.12",
"chunk-manifest-webpack-plugin": "0.1.0", "chunk-manifest-webpack-plugin": "0.1.0",
@@ -190,6 +190,7 @@
"webpack-hot-middleware": "^2.12.2", "webpack-hot-middleware": "^2.12.2",
"webpack-manifest-plugin": "^1.0.0", "webpack-manifest-plugin": "^1.0.0",
"webpack-stream": "^3.1.0", "webpack-stream": "^3.1.0",
"webpack-visualizer-plugin": "^0.1.11",
"yargs": "^7.0.1" "yargs": "^7.0.1"
}, },
"snyk": true, "snyk": true,

View File

@@ -619,6 +619,7 @@
"<code>&#60img src=\"https://www.your-image-source.com/your-image.jpg\"&#62</code>", "<code>&#60img src=\"https://www.your-image-source.com/your-image.jpg\"&#62</code>",
"Note that in most cases, <code>img</code> elements are self-closing.", "Note that in most cases, <code>img</code> elements are self-closing.",
"All <code>img</code> elements <strong>must</strong> have an <code>alt</code> attribute. The text inside an <code>alt</code> attribute is used for screen readers to improve accessibility and is displayed if the image fails to load.", "All <code>img</code> elements <strong>must</strong> have an <code>alt</code> attribute. The text inside an <code>alt</code> attribute is used for screen readers to improve accessibility and is displayed if the image fails to load.",
"Ideally the <code>alt</code> attribute should not contain special chars unless needed.",
"Let's add an <code>alt</code> attribute to our <code>img</code> example above:", "Let's add an <code>alt</code> attribute to our <code>img</code> example above:",
"<code>&#60img src=\"https://www.your-image-source.com/your-image.jpg\" alt=\"Author standing on a beach with two thumbs up.\"&#62</code>", "<code>&#60img src=\"https://www.your-image-source.com/your-image.jpg\" alt=\"Author standing on a beach with two thumbs up.\"&#62</code>",
"<hr>", "<hr>",
@@ -1768,7 +1769,8 @@
"Each of your radio buttons should be nested within its own <code>label</code> element.", "Each of your radio buttons should be nested within its own <code>label</code> element.",
"All related radio buttons should have the same <code>name</code> attribute.", "All related radio buttons should have the same <code>name</code> attribute.",
"Here's an example of a radio button:", "Here's an example of a radio button:",
"<code>&#60;label&#62;&#60;input type=\"radio\" name=\"indoor-outdoor\"&#62; Indoor&#60;/label&#62;</code>", "<blockquote>&#60;label for=\"indoor\"&#62; <br> &#60;input type=\"radio\" name=\"indoor-outdoor\"&#62;Indoor <br>&#60;/label&#62;</blockquote>",
"It is considered best practice to set the <code>for</code> attribute for assistive technologies to be able to link the relationship between the label and the child elements such as an <code>input</code> in this case.",
"<hr>", "<hr>",
"Add a pair of radio buttons to your form. One should have the option of <code>indoor</code> and the other should have the option of <code>outdoor</code>. Both should share the <code>name</code> attribute of <code>indoor-outdoor</code>." "Add a pair of radio buttons to your form. One should have the option of <code>indoor</code> and the other should have the option of <code>outdoor</code>. Both should share the <code>name</code> attribute of <code>indoor-outdoor</code>."
], ],

View File

@@ -8,9 +8,9 @@
"title": "Claim Your Responsive Web Design Certificate", "title": "Claim Your Responsive Web Design Certificate",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/GjTPLxI.jpg",
"An image of our Responsive Web Design Certificate", "An image of our Responsive Web Design Certificate",
"This challenge will give you your verified Responsive Web Design Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", "This challenge will give you your verified Responsive Web Design Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our responsive web design projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -20,9 +20,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/UedoV2G.jpg", "//i.imgur.com/cyRVnUa.jpg",
"An image of the text \"Front End Development Certificate requirements\"", "An image of the text \"Responsive Web Design requirements\"",
"Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. Click the button below to verify this.", "Let's confirm that you have completed all of our responsive web design projects. Click the button below to verify this.",
"#" "#"
], ],
[ [
@@ -36,11 +36,11 @@
{ {
"properties": [ "properties": [
"isHonest", "isHonest",
"isFrontEndCert" "isRespWebDesignCert"
], ],
"apis": [ "apis": [
"/certificate/honest", "/certificate/honest",
"/certificate/verify/front-end" "/certificate/verify/responsive-web-design"
], ],
"stepIndex": [ "stepIndex": [
1, 1,
@@ -77,9 +77,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces", "title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/GjTPLxI.jpg",
"Una imagen que muestra nuestro certificado de Desarrollo de interfaces", "An image of our Responsive Web Design Certificate",
"Este desafío te otorga tu certificado autenticado de Desarrollo de interfaces. Antes de que podamos emitir tu certificado, debemos verificar que has completado todos los desafíos básicos e intermedios de diseño de algoritmos, y todos los proyectos básicos e intermedios de desarrollo de interfaces. También debes aceptar nuestro Juramento de honestidad académica. Pulsa el botón siguiente para iniciar este proceso.", "This challenge will give you your verified Responsive Web Design Certificate. Before we issue your certificate, we must verify that you have completed all of our responsive web design projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -89,9 +89,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/14F2Van.jpg", "//i.imgur.com/cyRVnUa.jpg",
"Una imagen del texto \"Front End Development Certificate requirements\"", "An image of the text \"Responsive Web Design requirements\"",
"Confirmemos que has completado todos nuestros desafíos básicos e intermedios de diseño de algoritmos, y todos nuestros proyectos básicos e intermedios de desarrollo de interfaces. Pulsa el botón siguiente para hacer la verificación.", "Let's confirm that you have completed all of our responsive web design projects. Click the button below to verify this.",
"#" "#"
], ],
[ [

View File

@@ -686,7 +686,7 @@
}, },
{ {
"id": "bd7993c9ca9feddfaeb7bdef", "id": "bd7993c9ca9feddfaeb7bdef",
"title": "Divide one Decimal by Another with JavaScript", "title": "Divide One Decimal by Another with JavaScript",
"description": [ "description": [
"Now let's divide one decimal by another.", "Now let's divide one decimal by another.",
"<hr>", "<hr>",
@@ -704,7 +704,7 @@
"tests": [ "tests": [
"assert(quotient === 2.2, 'message: The variable <code>quotient</code> should equal <code>2.2</code>');", "assert(quotient === 2.2, 'message: The variable <code>quotient</code> should equal <code>2.2</code>');",
"assert(/4\\.40*\\s*\\/\\s*2\\.*0*/.test(code), 'message: You should use the <code>/</code> operator to divide 4.4 by 2');", "assert(/4\\.40*\\s*\\/\\s*2\\.*0*/.test(code), 'message: You should use the <code>/</code> operator to divide 4.4 by 2');",
"assert(code.match(/quotient/g).length === 3, 'message: The quotient variable should only be assigned once');" "assert(code.match(/quotient/g).length === 1, 'message: The quotient variable should only be assigned once');"
], ],
"type": "waypoint", "type": "waypoint",
"challengeType": 1, "challengeType": 1,
@@ -5411,7 +5411,7 @@
"Now, take a look at a <code>do...while</code> loop.", "Now, take a look at a <code>do...while</code> loop.",
"<blockquote>var ourArray = []; <br>var i = 5;<br>do {<br> ourArray.push(i);<br> i++;<br>} while (i < 5);</blockquote>", "<blockquote>var ourArray = []; <br>var i = 5;<br>do {<br> ourArray.push(i);<br> i++;<br>} while (i < 5);</blockquote>",
"In this case, we initialize the value of <code>i</code> as 5, just like we did with the while loop. When we get to the next line, there is no check for the value of <code>i</code>, so we go to the code inside the curly braces and execute it. We will add one element to the array and increment <code>i</code> before we get to the condition check. Then, when we get to checking if <code>i < 5</code> see that <code>i</code> is now 6, which fails the conditional check. So we exit the loop and are done. At the end of the above example, the value of <code>ourArray</code> is <code>[5]</code>.", "In this case, we initialize the value of <code>i</code> as 5, just like we did with the while loop. When we get to the next line, there is no check for the value of <code>i</code>, so we go to the code inside the curly braces and execute it. We will add one element to the array and increment <code>i</code> before we get to the condition check. Then, when we get to checking if <code>i < 5</code> see that <code>i</code> is now 6, which fails the conditional check. So we exit the loop and are done. At the end of the above example, the value of <code>ourArray</code> is <code>[5]</code>.",
"Essenially, a <code>do...while</code> loop ensures that the code inside the loop will run at least once.", "Essentially, a <code>do...while</code> loop ensures that the code inside the loop will run at least once.",
"Let's try getting a <code>do...while</code> loop to work by pushing values to an array.", "Let's try getting a <code>do...while</code> loop to work by pushing values to an array.",
"<hr>", "<hr>",
"Change the <code>while</code> loop in the code to a <code>do...while</code> loop so that the loop will push the number 10 to <code>myArray</code>, and <code>i</code> will be equal to <code>11</code> when your code finishes running." "Change the <code>while</code> loop in the code to a <code>do...while</code> loop so that the loop will push the number 10 to <code>myArray</code>, and <code>i</code> will be equal to <code>11</code> when your code finishes running."

View File

@@ -4,13 +4,13 @@
"time": "5 minutes", "time": "5 minutes",
"challenges": [ "challenges": [
{ {
"id": "587d7b7f367417b2b2512b25", "id": "5a34371eb8853b934b0d9803",
"title": "Claim Your JavaScript Algorithms and Data Structures Certificate", "title": "Claim Your JavaScript Algorithms and Data Structures Certificate",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/EzMrezJ.jpg",
"An image of our Front End Development Certificate", "An image of our JavaScript Algorithms and Data Structures Certificate",
"This challenge will give you your verified Front End Development Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", "This challenge will give you your verified JavaScript Algorithms and Data Structures Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -20,15 +20,15 @@
"#" "#"
], ],
[ [
"//i.imgur.com/UedoV2G.jpg", "//i.imgur.com/rx2gKfB.jpg",
"An image of the text \"Front End Development Certificate requirements\"", "An image of the text \"JavaScript Algorithms and Data Structures requirements\"",
"Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. Click the button below to verify this.", "Let's confirm that you have completed all of our JavaScript Algorithms and Data Structures projects. Click the button below to verify this.",
"#" "#"
], ],
[ [
"//i.imgur.com/Q5Za9U6.jpg", "//i.imgur.com/Q5Za9U6.jpg",
"An image of the word \"Congratulations\"", "An image of the word \"Congratulations\"",
"Congratulations! We've added your Front End Development Certificate to your portfolio page. Unless you choose to hide your solutions, this certificate will remain publicly visible and verifiable.", "Congratulations! We've added your JavaScript Algorithms and Data Structures Certificate to your portfolio page. Unless you choose to hide your solutions, this certificate will remain publicly visible and verifiable.",
"" ""
] ]
], ],
@@ -36,11 +36,11 @@
{ {
"properties": [ "properties": [
"isHonest", "isHonest",
"isFrontEndCert" "isJsAlgoDataStructCert"
], ],
"apis": [ "apis": [
"/certificate/honest", "/certificate/honest",
"/certificate/verify/front-end" "/certificate/verify/javascript-algorithms-data-structures"
], ],
"stepIndex": [ "stepIndex": [
1, 1,
@@ -49,170 +49,34 @@
} }
], ],
"tests": [ "tests": [
{
"id": "a202eed8fc186c8434cb6d61",
"title": "Reverse a String"
},
{
"id": "a302f7aae1aa3152a5b413bc",
"title": "Factorialize a Number"
},
{ {
"id": "aaa48de84e1ecc7c742e1124", "id": "aaa48de84e1ecc7c742e1124",
"title": "Check for Palindromes" "title": "Palindrome Checker"
},
{
"id": "a26cbbe9ad8655a977e1ceb5",
"title": "Find the Longest Word in a String"
},
{
"id": "ab6137d4e35944e21037b769",
"title": "Title Case a Sentence"
},
{
"id": "a789b3483989747d63b0e427",
"title": "Return Largest Numbers in Arrays"
},
{
"id": "acda2fb1324d9b0fa741e6b5",
"title": "Confirm the Ending"
},
{
"id": "afcc8d540bea9ea2669306b6",
"title": "Repeat a string repeat a string"
},
{
"id": "ac6993d51946422351508a41",
"title": "Truncate a string"
},
{
"id": "a9bd25c716030ec90084d8a1",
"title": "Chunky Monkey"
},
{
"id": "579e2a2c335b9d72dd32e05c",
"title": "Splice and Slice"
},
{
"id": "af2170cad53daa0770fabdea",
"title": "Mutations"
},
{
"id": "adf08ec01beb4f99fc7a68f2",
"title": "Falsy Bouncer"
},
{
"id": "a39963a4c10bc8b4d4f06d7e",
"title": "Seek and Destroy"
},
{
"id": "a24c1a4622e3c05097f71d67",
"title": "Where do I belong"
},
{
"id": "a3566b1109230028080c9345",
"title": "Sum All Numbers in a Range"
},
{
"id": "a5de63ebea8dbee56860f4f2",
"title": "Diff Two Arrays"
}, },
{ {
"id": "a7f4d8f2483413a6ce226cac", "id": "a7f4d8f2483413a6ce226cac",
"title": "Roman Numeral Converter" "title": "Roman Numeral Converter"
}, },
{
"id": "a8e512fbe388ac2f9198f0fa",
"title": "Wherefore art thou"
},
{
"id": "a0b5010f579e69b815e7c5d6",
"title": "Search and Replace"
},
{
"id": "aa7697ea2477d1316795783b",
"title": "Pig Latin"
},
{
"id": "afd15382cdfb22c9efe8b7de",
"title": "DNA Pairing"
},
{
"id": "af7588ade1100bde429baf20",
"title": "Missing letters"
},
{
"id": "a77dbc43c33f39daa4429b4f",
"title": "Boo who"
},
{
"id": "a105e963526e7de52b219be9",
"title": "Sorted Union"
},
{
"id": "a6b0bb188d873cb2c8729495",
"title": "Convert HTML Entities"
},
{
"id": "a103376db3ba46b2d50db289",
"title": "Spinal Tap Case"
},
{
"id": "a5229172f011153519423690",
"title": "Sum All Odd Fibonacci Numbers"
},
{
"id": "a3bfc1673c0526e06d3ac698",
"title": "Sum All Primes"
},
{
"id": "ae9defd7acaf69703ab432ea",
"title": "Smallest Common Multiple"
},
{
"id": "a6e40f1041b06c996f7b2406",
"title": "Finders Keepers"
},
{
"id": "a5deed1811a43193f9f1c841",
"title": "Drop it"
},
{
"id": "ab306dbdcc907c7ddfc30830",
"title": "Steamroller"
},
{
"id": "a8d97bd4c764e91f9d2bda01",
"title": "Binary Agents"
},
{
"id": "a10d2431ad0c6a099a4b8b52",
"title": "Everything Be True"
},
{
"id": "a97fd23d9b809dac9921074f",
"title": "Arguments Optional"
},
{ {
"id": "56533eb9ac21ba0edf2244e2", "id": "56533eb9ac21ba0edf2244e2",
"title": "Caesars Cipher" "title": "Caesars Cipher"
}, },
{ {
"id": "a2f1d72d9b908d0bd72bb9f6", "id": "aff0395860f5d3034dc0bfc9",
"title": "Make a Person" "title": "Telephone Number Validator"
}, },
{ {
"id": "af4afb223120f7348cdfc9fd", "id": "aa2e6f85cab2ab736c9a9b24",
"title": "Map the Debris" "title": "Cash Register"
} }
], ],
"type": "Waypoint", "type": "Waypoint",
"challengeType": 7, "challengeType": 7,
"descriptionEs": [ "descriptionEs": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/EzMrezJ.jpg",
"Una imagen que muestra nuestro certificado de Desarrollo de interfaces", "Una imagen que muestra nuestro certificado de Desarrollo de interfaces",
"Este desafío te otorga tu certificado autenticado de Desarrollo de interfaces. Antes de que podamos emitir tu certificado, debemos verificar que has completado todos los desafíos básicos e intermedios de diseño de algoritmos, y todos los proyectos básicos e intermedios de desarrollo de interfaces. También debes aceptar nuestro Juramento de honestidad académica. Pulsa el botón siguiente para iniciar este proceso.", "This challenge will give you your verified JavaScript Algorithms and Data Structures Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -222,9 +86,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/14F2Van.jpg", "//i.imgur.com/rx2gKfB.jpg",
"Una imagen del texto \"Front End Development Certificate requirements\"", "An image of the text \"JavaScript Algorithms and Data Structures requirements\"",
"Confirmemos que has completado todos nuestros desafíos básicos e intermedios de diseño de algoritmos, y todos nuestros proyectos básicos e intermedios de desarrollo de interfaces. Pulsa el botón siguiente para hacer la verificación.", "Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges. Click the button below to verify this.",
"#" "#"
], ],
[ [

View File

@@ -126,7 +126,7 @@
"" ""
], ],
"tests": [ "tests": [
"assert(code.match(/console\\.log\\(typeof[\\( ].*\\)?\\);/g).length == 2, 'message: Your code should use <code>typeof</code> in two <code>console.log()</code> statements to check the type of the variables.');", "assert(code.match(/console\\.log\\(typeof[\\( ].*\\)?\\)/g).length == 2, 'message: Your code should use <code>typeof</code> in two <code>console.log()</code> statements to check the type of the variables.');",
"assert(code.match(/typeof[\\( ]seven\\)?/g), 'message: Your code should use <code>typeof</code> to check the type of the variable <code>seven</code>.');", "assert(code.match(/typeof[\\( ]seven\\)?/g), 'message: Your code should use <code>typeof</code> to check the type of the variable <code>seven</code>.');",
"assert(code.match(/typeof[\\( ]three\\)?/g), 'message: Your code should use <code>typeof</code> to check the type of the variable <code>three</code>.');" "assert(code.match(/typeof[\\( ]three\\)?/g), 'message: Your code should use <code>typeof</code> to check the type of the variable <code>three</code>.');"
], ],

View File

@@ -25,30 +25,38 @@
}, },
{ {
"id": "587d7b87367417b2b2512b3f", "id": "587d7b87367417b2b2512b3f",
"title": "Explore Problems with the var Keyword", "title": "Explore Differences Between the var and let Keywords",
"description": [ "description": [
"One of the biggest problems with declaring variables with the <code>var</code> keyword is that you can overwrite variable declarations without an error.", "One of the biggest problems with declaring variables with the <code>var</code> keyword is that you can overwrite variable declarations without an error.",
"<blockquote>var camper = 'James';<br>var camper = 'David';<br>console.log(camper);<br>// logs 'David'</blockquote>", "<blockquote>var camper = 'James';<br>var camper = 'David';<br>console.log(camper);<br>// logs 'David'</blockquote>",
"In a small application, you might not run into this type of problem, but when your code becomes larger, you might accidently overwrite a variable that you did not intend to overwrite. Because this behaviour does not throw an error, searching and fixing bugs becomes more difficult.", "As you can see in the code above, the <code>camper</code> variable is originally declared as <code>James</code> and then overridden to be <code>David</code>.",
"Another problem with the <code>var</code> keyword is that it is hoisted to the top of your code when it compiles. This means that you can use a variable before you declare it.", "In a small application, you might not run into this type of problem, but when your code becomes larger, you might accidently overwrite a variable that you did not intend to overwrite.",
"<blockquote>console.log(camper);<br>var camper = 'David';<br>// logs undefined</blockquote>", "Because this behavior does not throw an error, searching and fixing bugs becomes more difficult.<br>",
"The code runs in the following order:", "A new keyword called <code>let</code> was introduced in ES6 to solve this potential issue with the <code>var</code> keyword.",
"<ol><li>The variable <code>camper</code> is declared as undefined.</li><li>The value of <code>camper</code> is logged.</li><li>David is assigned to <code>camper</code>.</li></ol>", "If you were to replace <code>var</code> with <code>let</code> in the variable declarations of the code above, the result would be an error.",
"This code will run without an error.", "<blockquote>let camper = 'James';<br>let camper = 'David'; // throws an error</blockquote>",
"A new keyword called <code>let</code> was introduced in ES6 to solve the problems with the <code>var</code> keyword. With the <code>let</code> keyword, all the examples we just saw will cause an error to appear. We can no longer overwrite variables or use a variable before we declare it. Some modern browsers require you to add <code>\"use strict\";</code> to the top of your code before you can use the new features of ES6.", "This error can be seen in the console of your browser.",
"Let's try using the <code>let</code> keyword.", "So unlike <code>var</code>, when using <code>let</code>, a variable with the same name can only be declared once.",
"<hr>", "<hr>",
"Fix the code so that it only uses the <code>let</code> keyword and makes the errors go away.", "Update the code so it only uses the <code>let</code> keyword.",
"<strong>Note</strong><br>Remember to add <code>\"use strict\";</code> to the top of your code." "<strong>Note</strong><br>Remember that since <code>let</code> prevents variables from being overridden, you will need to remove one of the declarations entirely."
], ],
"challengeSeed": [ "challengeSeed": [
"var favorite = redNosedReindeer + \" is Santa's favorite reindeer.\";", "var catName;",
"var redNosedReindeer = \"Rudolph\";", "var quote;",
"var redNosedReindeer = \"Comet\";" "function catTalk() {",
" \"use strict\";",
"",
" catName = \"Oliver\";",
" quote = catName + \" says Meow!\";",
"",
"}",
"catTalk();"
], ],
"tests": [ "tests": [
"assert(redNosedReindeer === \"Rudolph\", 'message: <code>redNosedReindeer</code> should be Rudolph.');", "getUserInput => assert(!getUserInput('index').match(/var/g),'message: <code>var</code> does not exist in code.');",
"assert(favorite === \"Rudolph is Santa's favorite reindeer.\", \"message: <code>favorite</code> should return Santa's favorite reindeer.\");" "assert(catName === \"Oliver\", 'message: <code>catName</code> should be <code>Oliver</code>.');",
"assert(quote === \"Oliver says Meow!\", 'message: <code>quote</code> should be <code>\"Oliver says Meow!\"</code>');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -72,11 +80,12 @@
"<code>i</code> is not defined because it was not declared in the global scope. It is only declared within the for loop statement. <code>printNumTwo()</code> returned the correct value because three different <code>i</code> variables with unique values (0, 1, and 2) were created by the <code>let</code> keyword within the loop statement.", "<code>i</code> is not defined because it was not declared in the global scope. It is only declared within the for loop statement. <code>printNumTwo()</code> returned the correct value because three different <code>i</code> variables with unique values (0, 1, and 2) were created by the <code>let</code> keyword within the loop statement.",
"<hr>", "<hr>",
"Fix the code so that <code>i</code> declared in the if statement is a separate variable than <code>i</code> declared in the first line of the function. Be certain not to use the <code>var</code> keyword anywhere in your code.", "Fix the code so that <code>i</code> declared in the if statement is a separate variable than <code>i</code> declared in the first line of the function. Be certain not to use the <code>var</code> keyword anywhere in your code.",
"<strong>Note</strong><br>Remember to add <code>\"use strict\";</code> to the top of your code.",
"This exercise is designed to illustrate the difference between how <code>var</code> and <code>let</code> keywords assign scope to the declared variable. When programming a function similar to the one used in this exercise, it is often better to use different variable names to avoid confusion." "This exercise is designed to illustrate the difference between how <code>var</code> and <code>let</code> keywords assign scope to the declared variable. When programming a function similar to the one used in this exercise, it is often better to use different variable names to avoid confusion."
], ],
"challengeSeed": [ "challengeSeed": [
"",
"function checkScope() {", "function checkScope() {",
"\"use strict\";",
" var i = \"function scope\";", " var i = \"function scope\";",
" if (true) {", " if (true) {",
" i = \"block scope\";", " i = \"block scope\";",
@@ -84,13 +93,11 @@
" }", " }",
" console.log(\"Function scope i is: \", i);", " console.log(\"Function scope i is: \", i);",
" return i;", " return i;",
"}", "}"
"// only change the code above this line",
"checkScope();"
], ],
"tests": [ "tests": [
"// TEMPORARILY COMMENTED OUT: assert(!/var/g.test(code) && /let/g.test(code), 'message: The <code>var</code> keyword should be replaced with <code>let</code>. (This test is temporarily disabled)');", "getUserInput => assert(!getUserInput('index').match(/var/g),'message: <code>var</code> does not exist in code.');",
"assert(code.match(/(i\\s*=\\s*).*\\s*.*\\s*.*\\1('|\")block\\s*scope\\2/g), 'message: The variable <code>i</code> declared in the if statement should equal \"block scope\".');", "getUserInput => assert(getUserInput('index').match(/(i\\s*=\\s*).*\\s*.*\\s*.*\\1('|\")block\\s*scope\\2/g), 'message: The variable <code>i</code> declared in the if statement should equal \"block scope\".');",
"assert(checkScope() === \"function scope\", 'message: <code>checkScope()</code> should return \"function scope\"');" "assert(checkScope() === \"function scope\", 'message: <code>checkScope()</code> should return \"function scope\"');"
], ],
"type": "waypoint", "type": "waypoint",
@@ -107,27 +114,28 @@
"<blockquote>\"use strict\"<br>const FAV_PET = \"Cats\";<br>FAV_PET = \"Dogs\"; // returns error</blockquote>", "<blockquote>\"use strict\"<br>const FAV_PET = \"Cats\";<br>FAV_PET = \"Dogs\"; // returns error</blockquote>",
"As you can see, trying to reassign a variable declared with <code>const</code> will throw an error. You should always name variables you don't want to reassign using the <code>const</code> keyword. This helps when you accidentally attempt to reassign a variable that is meant to stay constant. A common practice is to name your constants in all upper-cases and with an underscore to separate words (e.g. <code>EXAMPLE_VARIABLE</code>).", "As you can see, trying to reassign a variable declared with <code>const</code> will throw an error. You should always name variables you don't want to reassign using the <code>const</code> keyword. This helps when you accidentally attempt to reassign a variable that is meant to stay constant. A common practice is to name your constants in all upper-cases and with an underscore to separate words (e.g. <code>EXAMPLE_VARIABLE</code>).",
"<hr>", "<hr>",
"Change the code so that all variables are declared using <code>let</code> or <code>const</code>. Use <code>let</code> when you want the variable to change, and <code>const</code> when you want the variable to remain constant. Also, rename variables declared with <code>const</code> to conform to common practices.", "Change the code so that all variables are declared using <code>let</code> or <code>const</code>. Use <code>let</code> when you want the variable to change, and <code>const</code> when you want the variable to remain constant. Also, rename variables declared with <code>const</code> to conform to common practices, meaning constants should be in all caps"
"<strong>Note</strong><br>Don't forget to add <code>\"use strict\";</code> to the top of your code."
], ],
"challengeSeed": [ "challengeSeed": [
"// change 'var' to 'let' or 'const'", "function printManyTimes(str) {",
"// rename constant variables", " \"use strict\";",
"var pi = 3.14;", "",
"var radius = 10;", " // change code below this line",
"var calculateCircumference = function(r) {", "",
" var diameter = 2 * r;", " var sentence = str + \" is cool!\";",
" var result = pi * diameter;", " for(var i = 0; i < str.length; i+=2) {",
" return result;", " console.log(str2);",
"};", " }",
"// Test your code", "",
"console.log(calculateCircumference(radius));" " // change code above this line",
"",
"}",
"printManyTimes(\"FreeCodeCamp\");"
], ],
"tests": [ "tests": [
"// Test user replaced all var keyword", "getUserInput => assert(!getUserInput('index').match(/var/g),'message: <code>var</code> does not exist in code.');",
"// Test PI is const", "getUserInput => assert(getUserInput('index').match(/(const SENTENCE)/g), 'message: <code>SENTENCE</code> should be a constant variable (by using <code>const</code>).');",
"// Test calculateCircumference is const", "getUserInput => assert(getUserInput('index').match(/(let i)/g), 'message: <code>i</code> should be a variable only defined within the for loop scope (by using<code>let</code>).');"
"// Test pi and calculateCircumference has been renamed"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -144,23 +152,24 @@
"<blockquote>\"use strict\";<br>const s = [5, 6, 7];<br>s = [1, 2, 3]; // throws error, trying to assign a const<br>s[2] = 45; // works just as it would with an array declared with var or let<br>console.log(s); // returns [5, 6, 45]</blockquote>", "<blockquote>\"use strict\";<br>const s = [5, 6, 7];<br>s = [1, 2, 3]; // throws error, trying to assign a const<br>s[2] = 45; // works just as it would with an array declared with var or let<br>console.log(s); // returns [5, 6, 45]</blockquote>",
"As you can see, you can mutate the object <code>[5, 6, 7]</code> itself and the variable <code>s</code> will still point to the altered array <code>[5, 6, 45]</code>. Like all arrays, the array elements in <code>s</code> are mutable, but because <code>const</code> was used, you cannot use the variable identifier <code>s</code> to point to a different array using the assignment operator.", "As you can see, you can mutate the object <code>[5, 6, 7]</code> itself and the variable <code>s</code> will still point to the altered array <code>[5, 6, 45]</code>. Like all arrays, the array elements in <code>s</code> are mutable, but because <code>const</code> was used, you cannot use the variable identifier <code>s</code> to point to a different array using the assignment operator.",
"<hr>", "<hr>",
"An array is declared as <code>const s = [5, 7, 2]</code>. Change the array to <code>[2, 5, 7]</code> using various element assignment.", "An array is declared as <code>const s = [5, 7, 2]</code>. Change the array to <code>[2, 5, 7]</code> using various element assignment."
"<strong>Note</strong><br>Don't forget to add <code>\"use strict\";</code> to the top of your code."
], ],
"challengeSeed": [ "challengeSeed": [
"const s = [5, 7, 2];", "const s = [5, 7, 2];",
"// change code below this line", "function editInPlace() {",
" \"use strict\";",
" // change code below this line",
"", "",
"s = [2, 5, 7];", " // s = [2, 5, 7]; <- this is invalid",
"", "",
"// change code above this line", " // change code above this line",
"// Test your code", "}",
"console.log(s);" "editInPlace();"
], ],
"tests": [ "tests": [
"assert(code.match(/const/g), 'message: Do not replace <code>const</code> keyword.');", "getUserInput => assert(getUserInput('index').match(/const/g), 'message: Do not replace <code>const</code> keyword.');",
"assert(code.match(/const\\s+s/g), 'message: <code>s</code> is declared with <code>const</code>.');", "getUserInput => assert(getUserInput('index').match(/const\\s+s/g), 'message: <code>s</code> should be a constant variable (by using <code>const</code>).');",
"assert(code.match(/const\\s+s\\s*?=\\s*?\\[\\s*?2\\s*?,\\s*?5\\s*?,\\s*?7\\s*?\\]\\s*?;/g), 'message: Do not change the original array declaration.');", "getUserInput => assert(getUserInput('index').match(/const\\s+s\\s*=\\s*\\[\\s*5\\s*,\\s*7\\s*,\\s*2\\s*\\]\\s*;?/g), 'message: Do not change the original array declaration.');",
"assert.deepEqual(s, [2, 5, 7], 'message: <code>s</code> should be equal to <code>[2, 5, 7]</code>.');" "assert.deepEqual(s, [2, 5, 7], 'message: <code>s</code> should be equal to <code>[2, 5, 7]</code>.');"
], ],
"type": "waypoint", "type": "waypoint",
@@ -179,22 +188,29 @@
"In this challenge you are going to use <code>Object.freeze</code> to prevent mathematical constants from changing. You need to freeze <code>MATH_CONSTANTS</code> object so that noone is able alter the value of <code>PI</code> or add any more properties to it." "In this challenge you are going to use <code>Object.freeze</code> to prevent mathematical constants from changing. You need to freeze <code>MATH_CONSTANTS</code> object so that noone is able alter the value of <code>PI</code> or add any more properties to it."
], ],
"challengeSeed": [ "challengeSeed": [
"const MATH_CONSTANTS = {", "function freezeObj() {",
" PI: 3.14", " \"use strict\";",
"};", " const MATH_CONSTANTS = {",
"// change code below this line", " PI: 3.14",
" };",
" // change code below this line",
"", "",
"", "",
"// change code above this line", " // change code above this line",
"MATH_CONSTANTS.PI = 99;", " try {",
"// Test your code", " MATH_CONSTANTS.PI = 99;",
"console.log(MATH_CONSTANTS.PI);// should show 3.14" " } catch( ex ) {",
" console.log(ex);",
" }",
" return MATH_CONSTANTS.PI;",
"}",
"const PI = freezeObj();"
], ],
"tests": [ "tests": [
"// Do not replace <code>const</code> keyword.", "getUserInput => assert(getUserInput('index').match(/const/g), 'message: Do not replace <code>const</code> keyword.');",
"// <code>MATH_CONSTANTS</code> is declared with <code>const</code>.", "getUserInput => assert(getUserInput('index').match(/const\\s+MATH_CONSTANTS/g), 'message: <code>MATH_CONSTANTS</code> should be a constant variable (by using <code>const</code>).');",
"// Do not change original <code>MATH_CONSTANTS</code>", "getUserInput => assert(getUserInput('index').match(/const\\s+MATH_CONSTANTS\\s+=\\s+{\\s+PI:\\s+3.14\\s+};/g), 'message: Do not change original <code>MATH_CONSTANTS</code>.');",
"assert.deepEqual(MATH_CONSTANTS, {PI: 3.14}, 'message: <code>MATH_CONSTANTS.PI</code> should be equal to <code>3.14</code>.');" "assert(PI === 3.14, 'message: <code>PI</code> equals <code>3.14</code>.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Aug 12, 2017", "releasedOn": "Aug 12, 2017",
@@ -214,26 +230,20 @@
"<blockquote>const myFunc= () => \"value\"</blockquote>", "<blockquote>const myFunc= () => \"value\"</blockquote>",
"This code will still return <code>value</code> by default.", "This code will still return <code>value</code> by default.",
"<hr>", "<hr>",
"Rewrite the function assigned to the variable <code>magic</code> which returns a new <code>Date()</code> to use arrow function syntax. Also make sure nothing is defined using the keyword <code>var</code>.", "Rewrite the function assigned to the variable <code>magic</code> which returns a new <code>Date()</code> to use arrow function syntax. Also make sure nothing is defined using the keyword <code>var</code>."
"Note",
"Don't forget to use strict mode."
], ],
"challengeSeed": [ "challengeSeed": [
"// change code below this line",
"var magic = function() {", "var magic = function() {",
" \"use strict\";",
" return new Date();", " return new Date();",
"}", "};"
"// change code above this line",
"// test your code",
"console.log(magic());"
], ],
"tests": [ "tests": [
"// Test user did replace var keyword", "getUserInput => assert(!getUserInput('index').match(/var/g), 'message: User did replace <code>var</code> keyword.');",
"// Test magic is const", "getUserInput => assert(getUserInput('index').match(/const\\s+magic/g), 'message: <code>magic</code> should be a constant variable (by using <code>const</code>).');",
"// Test magic is a function", "assert(typeof magic === 'function', 'message: <code>magic</code> is a <code>function</code>.');",
"// Test magic() returns the correct date", "assert(magic().getDate() == new Date().getDate(), 'message: <code>magic()</code> returns correct date.');",
"// Test function keyword was not used", "getUserInput => assert(!getUserInput('index').match(/function/g), 'message: <code>function</code> keyword was not used.');"
"// Test arrow => was used"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -248,26 +258,22 @@
"<blockquote>// doubles input value and returns it<br>const doubler = (item) => item * 2;</blockquote>", "<blockquote>// doubles input value and returns it<br>const doubler = (item) => item * 2;</blockquote>",
"You can pass more than one argument into arrow functions as well.", "You can pass more than one argument into arrow functions as well.",
"<hr>", "<hr>",
"Rewrite the <code>myConcat</code> function which appends contents of <code>arr2</code> to <code>arr1</code> so that the function uses arrow function syntax.", "Rewrite the <code>myConcat</code> function which appends contents of <code>arr2</code> to <code>arr1</code> so that the function uses arrow function syntax."
"Note",
"Don't forget to use strict mode."
], ],
"challengeSeed": [ "challengeSeed": [
"// change code below this line",
"var myConcat = function(arr1, arr2) {", "var myConcat = function(arr1, arr2) {",
" \"use strict\";",
" return arr1.concat(arr2);", " return arr1.concat(arr2);",
"}", "};",
"// change code above this line",
"// test your code", "// test your code",
"console.log(myConcat([1, 2], [3, 4, 5]));" "console.log(myConcat([1, 2], [3, 4, 5]));"
], ],
"tests": [ "tests": [
"// Test user did replace var keyword", "getUserInput => assert(!getUserInput('index').match(/var/g), 'message: User did replace <code>var</code> keyword.');",
"// Test myConcat is const", "getUserInput => assert(getUserInput('index').match(/const\\s+myConcat/g), 'message: <code>myConcat</code> should be a constant variable (by using <code>const</code>).');",
"assert(typeof myConcat === \"function\", 'message: <code>myConcat</code> should be a function');", "assert(typeof myConcat === 'function', 'message: <code>myConcat</code> should be a function');",
"// Test myConcat() returns the correct array", "assert(() => { const a = myConcat([1], [2]); return a[0] == 1 && a[1] == 2; }, 'message: <code>myConcat()</code> returns the correct <code>array</code>');",
"// Test function keyword was not used", "getUserInput => assert(!getUserInput('index').match(/function/g), 'message: <code>function</code> keyword was not used.');"
"// Test arrow => was used"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -286,27 +292,29 @@
"<blockquote>FBPosts.filter((post) => post.thumbnail !== null && post.shares > 100 && post.likes > 500)</blockquote>", "<blockquote>FBPosts.filter((post) => post.thumbnail !== null && post.shares > 100 && post.likes > 500)</blockquote>",
"This code is more succinct and accomplishes the same task with fewer lines of code.", "This code is more succinct and accomplishes the same task with fewer lines of code.",
"<hr>", "<hr>",
"Use arrow function syntax to compute the square of only the positive integers (fractions are not integers) in the array <code>realNumberArray</code> and store the new array in the variable <code>squaredIntegers</code>.", "Use arrow function syntax to compute the square of only the positive integers (fractions are not integers) in the array <code>realNumberArray</code> and store the new array in the variable <code>squaredIntegers</code>."
"Note",
"Don't forget to use strict mode."
], ],
"challengeSeed": [ "challengeSeed": [
"const realNumberArray = [4, 5.6, -9.8, 3.14, 42, 6, 8.34];", "const realNumberArray = [4, 5.6, -9.8, 3.14, 42, 6, 8.34];",
"// change code below this line", "const squareList = (arr) => {",
"var squaredIntegers = realNumberArray;", " \"use strict\";",
"// change code above this line", " // change code below this line",
" const squaredIntegers = arr;",
" // change code above this line",
" return squaredIntegers;",
"};",
"// test your code", "// test your code",
"const squaredIntegers = squareList(realNumberArray);",
"console.log(squaredIntegers);" "console.log(squaredIntegers);"
], ],
"tests": [ "tests": [
"// Test user did replace <code>var</code> keyword", "getUserInput => assert(!getUserInput('index').match(/var/g), 'message: User did replace <code>var</code> keyword.');",
"// Test <code>squaredIntegers</code> is <code>const</code>", "getUserInput => assert(getUserInput('index').match(/const\\s+squaredIntegers/g), 'message: <code>squaredIntegers</code> should be a constant variable (by using <code>const</code>).');",
"assert(Array.isArray(squaredIntegers), 'message: <code>squaredIntegers</code> should be an array');", "assert(Array.isArray(squaredIntegers), 'message: <code>squaredIntegers</code> should be an <code>array</code>');",
"assert(squaredIntegers[0] === 16 && squaredIntegers[1] === 1764 && squaredIntegers[2] === 36, 'message: <code>squaredIntegers</code> should be <code>[16, 1764, 36]</code>');", "assert(squaredIntegers[0] === 16 && squaredIntegers[1] === 1764 && squaredIntegers[2] === 36, 'message: <code>squaredIntegers</code> should be <code>[16, 1764, 36]</code>');",
"// Test <code>function</code> keyword was not used", "getUserInput => assert(!getUserInput('index').match(/function/g), 'message: <code>function</code> keyword was not used.');",
"// Test arrow <code>=></code> was used", "getUserInput => assert(!getUserInput('index').match(/(for)|(while)/g), 'message: loop should not be used');",
"assert(!code.match(/(for)|(while)/g), 'message: loop should not be used');", "getUserInput => assert(getUserInput('index').match(/map|filter|reduce/g), 'message: <code>map</code>, <code>filter</code>, or <code>reduce</code> should be used');"
"assert(code.match(/map/g) && code.match(/filter/g), 'message: <code>map</code> and <code>filter</code> should be used');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -322,20 +330,22 @@
"<blockquote>function greeting(name = \"Anonymous\") {<br> return \"Hello \" + name;<br>}<br>console.log(greeting(\"John\")); // Hello John<br>console.log(greeting()); // Hello Anonymous</blockquote>", "<blockquote>function greeting(name = \"Anonymous\") {<br> return \"Hello \" + name;<br>}<br>console.log(greeting(\"John\")); // Hello John<br>console.log(greeting()); // Hello Anonymous</blockquote>",
"The default parameter kicks in when the argument is not specified (it is undefined). As you can see in the example above, the parameter <code>name</code> will receive its default value <code>\"Anonymous\"</code> when you do not provide a value for the parameter. You can add default values for as many parameters as you want.", "The default parameter kicks in when the argument is not specified (it is undefined). As you can see in the example above, the parameter <code>name</code> will receive its default value <code>\"Anonymous\"</code> when you do not provide a value for the parameter. You can add default values for as many parameters as you want.",
"<hr>", "<hr>",
"Modify the function <code>increment</code> by adding default parameters so that it will add 1 to <code>number</code> if <code>value</code> is not specified.", "Modify the function <code>increment</code> by adding default parameters so that it will add 1 to <code>number</code> if <code>value</code> is not specified."
"<strong>Note</strong><br>Don't forget to use strict mode."
], ],
"challengeSeed": [ "challengeSeed": [
"function increment(number, value) {", "const increment = (function() {",
" \"use strict\";",
" return function increment(number, value) {",
" return number + value;", " return number + value;",
"}", " };",
"})();",
"console.log(increment(5, 2)); // returns 7", "console.log(increment(5, 2)); // returns 7",
"console.log(increment(5)); // returns NaN" "console.log(increment(5)); // returns NaN"
], ],
"tests": [ "tests": [
"assert(increment(5, 2) === 7, \"The result of increment(5, 2) should be 7\");", "assert(increment(5, 2) === 7, 'message: The result of <code>increment(5, 2)</code> should be <code>7</code>.');",
"assert(increment(5) === 6, \"The result of increment(5) should be 6\");", "assert(increment(5) === 6, 'message: The result of <code>increment(5)</code> should be <code>6</code>.');",
"// Test default parameter was used for 'value'" "getUserInput => assert(getUserInput('index').match(/value\\s*=\\s*1/g), 'message: default parameter <code>1</code> was used for <code>value</code>.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -351,21 +361,24 @@
"<blockquote>function howMany(...args) {<br> return \"You have passed \" + args.length + \" arguments.\";<br>}<br>console.log(howMany(0, 1, 2)); // You have passed 3 arguments<br>console.log(howMany(\"string\", null, [1, 2, 3], { })); // You have passed 4 arguments.</blockquote>", "<blockquote>function howMany(...args) {<br> return \"You have passed \" + args.length + \" arguments.\";<br>}<br>console.log(howMany(0, 1, 2)); // You have passed 3 arguments<br>console.log(howMany(\"string\", null, [1, 2, 3], { })); // You have passed 4 arguments.</blockquote>",
"The rest operator eliminates the need to check the <code>args</code> array and allows us to apply <code>map()</code>, <code>filter()</code> and <code>reduce()</code> on the parameters array.", "The rest operator eliminates the need to check the <code>args</code> array and allows us to apply <code>map()</code>, <code>filter()</code> and <code>reduce()</code> on the parameters array.",
"<hr>", "<hr>",
"Modify the function <code>sum</code> so that is uses the rest operator and it works in the same way with any number of parameters.", "Modify the function <code>sum</code> so that is uses the rest operator and it works in the same way with any number of parameters."
"<strong>Note</strong><br>Don't forget to use strict mode."
], ],
"challengeSeed": [ "challengeSeed": [
"function sum(x, y, z) {", "const sum = (function() {",
" \"use strict\";",
" return function sum(x, y, z) {",
" const array = [ x, y, z ];", " const array = [ x, y, z ];",
" return array.reduce((a, b) => a + b, 0);", " return array.reduce((a, b) => a + b, 0);",
"}", " };",
"})();",
"console.log(sum(1, 2, 3)); // 6" "console.log(sum(1, 2, 3)); // 6"
], ],
"tests": [ "tests": [
"assert(sum(0,1,2) === 3, 'The result of sum(0,1,2) should be 3');", "assert(sum(0,1,2) === 3, 'message: The result of <code>sum(0,1,2)</code> should be 3');",
"assert(sum(1,2,3,4) === 10, 'The result of sum(1,2,3,4) should be 10');", "assert(sum(1,2,3,4) === 10, 'message: The result of <code>sum(1,2,3,4)</code> should be 10');",
"assert(sum(5) === 5, 'The result of sum(5) should be 5');", "assert(sum(5) === 5, 'message: The result of <code>sum(5)</code> should be 5');",
"assert(sum() === 0, 'The result of sum() should be 0');" "assert(sum() === 0, 'message: The result of <code>sum()</code> should be 0');",
"getUserInput => assert(getUserInput('index').match(/function\\s+sum\\s*\\(\\s*...args\\s*\\)\\s*{/g), 'message: The <code>sum</code> function uses the <code>...</code> spread operator on the <code>args</code> parameter.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -390,14 +403,17 @@
], ],
"challengeSeed": [ "challengeSeed": [
"const arr1 = ['JAN', 'FEB', 'MAR', 'APR', 'MAY'];", "const arr1 = ['JAN', 'FEB', 'MAR', 'APR', 'MAY'];",
"const arr2 = []; // change this line", "let arr2;",
"arr1.push('JUN');", "(function() {",
"console.log(arr2); // arr2 should not be affected" " \"use strict\";",
" arr2 = []; // change this line",
"})();",
"console.log(arr2);"
], ],
"tests": [ "tests": [
"// Test arr2 is correct copy of arr1", "assert(arr2.every((v, i) => v === arr1[i]), 'message: <code>arr2</code> is correct copy of <code>arr1</code>.');",
"// Test arr1 has changed", "getUserInput => assert(getUserInput('index').match(/\\[\\s*...arr1\\s*\\]/g),'message: <code>...</code> spread operator was used to duplicate <code>arr1</code>.');",
"// Test spread operator was used" "assert((arr1, arr2) => {arr1.push('JUN'); return arr2.length < arr1.length},'message: <code>arr2</code> remains unchanged when <code>arr1</code> is changed.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -408,7 +424,7 @@
"id": "587d7b89367417b2b2512b49", "id": "587d7b89367417b2b2512b49",
"title": "Use Destructuring Assignment to Assign Variables from Objects", "title": "Use Destructuring Assignment to Assign Variables from Objects",
"description": [ "description": [
"We earlier saw how spread operator can effectively spread, or unpack, the contents of the array.", "We saw earlier how spread operator can effectively spread, or unpack, the contents of the array.",
"We can do something similar with objects as well. <dfn>Destructuring assignment</dfn> is special syntax for neatly assigning values taken directly from an object to variables.", "We can do something similar with objects as well. <dfn>Destructuring assignment</dfn> is special syntax for neatly assigning values taken directly from an object to variables.",
"Consider the following ES5 code:", "Consider the following ES5 code:",
"<blockquote>var voxel = {x: 3.6, y: 7.4, z: 6.54 };<br>var x = voxel.x; // x = 3.6<br>var y = voxel.y; // y = 7.4<br>var z = voxel.z; // z = 6.54</blockquote>", "<blockquote>var voxel = {x: 3.6, y: 7.4, z: 6.54 };<br>var x = voxel.x; // x = 3.6<br>var y = voxel.y; // y = 7.4<br>var z = voxel.z; // z = 6.54</blockquote>",
@@ -418,18 +434,26 @@
"<blockquote>const { x : a, y : b, z : c } = voxel // a = 3.6, b = 7.4, c = 6.54</blockquote>", "<blockquote>const { x : a, y : b, z : c } = voxel // a = 3.6, b = 7.4, c = 6.54</blockquote>",
"You may read it as \"get the field <code>x</code> and copy the value into <code>a</code>,\" and so on.", "You may read it as \"get the field <code>x</code> and copy the value into <code>a</code>,\" and so on.",
"<hr>", "<hr>",
"Use destructuring to obtain the length of the string <code>greeting</code>" "Use destructuring to obtain the length of the input string <code>str</code>, and assign the length to <code>len</code> in line."
], ],
"challengeSeed": [ "challengeSeed": [
"const greeting = 'itadakimasu';", "function getLength(str) {",
"// change code below this line", " \"use strict\";",
"const length = 0; // change this", "",
"// change code above this line", " // change code below this line",
"console.log(length); // should be using destructuring" " const length = 0; // change this",
" // change code above this line",
"",
" return len; // you must assign length to len in line",
"",
"}",
"",
"console.log(getLength('FreeCodeCamp'));"
], ],
"tests": [ "tests": [
"// Test len is 11", "assert(typeof getLength('') === 'number', 'message: the function <code>getLength()</code> returns a number.');",
"// Test destructuring was used" "assert(getLength(\"FreeCodeCamp\") === 12, 'message: <code>getLength(\"FreeCodeCamp\")</code> should be <code>12</code>');",
"getUserInput => assert(getUserInput('index').match(/\\{\\s*length\\s*:\\s*len\\s*}\\s*=\\s*str/g),'message: destructuring with reassignment was used');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -448,18 +472,24 @@
"Use destructuring assignment to obtain <code>max</code> of <code>forecast.tomorrow</code> and assign it to <code>maxOfTomorrow</code>." "Use destructuring assignment to obtain <code>max</code> of <code>forecast.tomorrow</code> and assign it to <code>maxOfTomorrow</code>."
], ],
"challengeSeed": [ "challengeSeed": [
"const forecast = {", "const LOCAL_FORECAST = {",
" today: { min: 72, max: 83 },", " today: { min: 72, max: 83 },",
" tomorrow: { min: 73.3, max: 84.6 }", " tomorrow: { min: 73.3, max: 84.6 }",
"};", "};",
"// change code below this line", "",
"const maxOfTomorrow = undefined; // change this line", "function getMaxOfTmrw(forecast) {",
"// change code above this line", " \"use strict\";",
"console.log(maxOfTomorrow); // should be 84.6" " // change code below this line",
" const maxOfTomorrow = undefined; // change this line",
" // change code above this line",
" return maxOfTomorrow;",
"}",
"",
"console.log(getMaxOfTmrw(LOCAL_FORECAST)); // should be 84.6"
], ],
"tests": [ "tests": [
"// Test maxOfTomorrow to be 84.6", "assert(getMaxOfTmrw(LOCAL_FORECAST) === 84.6, 'message: <code>maxOfTomorrow</code> equals <code>84.6</code>');",
"// Test destructuring was used" "getUserInput => assert(getUserInput('index').match(/\\{\\s*tomorrow\\s*:\\s*\\{\\s*max\\s*:\\s*maxOfTomorrow\\s*\\}\\s*\\}\\s*=\\s*forecast/g),'message: nested destructuring was used');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -482,9 +512,12 @@
], ],
"challengeSeed": [ "challengeSeed": [
"let a = 8, b = 6;", "let a = 8, b = 6;",
"// change code below this line", "(() => {",
"", " \"use strict\";",
"// change code above this line", " // change code below this line",
" ",
" // change code above this line",
"})();",
"console.log(a); // should be 6", "console.log(a); // should be 6",
"console.log(b); // should be 8" "console.log(b); // should be 8"
], ],
@@ -512,17 +545,21 @@
], ],
"challengeSeed": [ "challengeSeed": [
"const source = [1,2,3,4,5,6,7,8,9,10];", "const source = [1,2,3,4,5,6,7,8,9,10];",
"// change code below this line", "function removeFirstTwo(list) {",
"const arr = source; // change this", " \"use strict\";",
"// change code below this line", " // change code below this line",
" arr = list; // change this",
" // change code below this line",
" return arr;",
"}",
"const arr = removeFirstTwo(source);",
"console.log(arr); // should be [3,4,5,6,7,8,9,10]", "console.log(arr); // should be [3,4,5,6,7,8,9,10]",
"console.log(source); // should be [1,2,3,4,5,6,7,8,9,10];" "console.log(source); // should be [1,2,3,4,5,6,7,8,9,10];"
], ],
"tests": [ "tests": [
"// Test arr is [3,4,5,6,7,8,9,10];", "assert(arr.every((v, i) => v === i + 3),'message: <code>arr</code> should be <code>[3,4,5,6,7,8,9,10]</code>');",
"// Test source is [1,2,3,4,5,6,7,8,9,10];", "getUserInput => assert(getUserInput('index').match(/\\[\\s*\\w\\s*,\\s*\\w\\s*,\\s*...arr\\s*\\]/g),'message: destructuring was used.');",
"// Test destructuring was used", "getUserInput => assert(!getUserInput('index').match(/Array.slice/g), 'message: <code>Array.slice()</code> was not used.');"
"// Test slice was not used"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -552,16 +589,24 @@
" min: -0.75,", " min: -0.75,",
" average: 35.85", " average: 35.85",
"};", "};",
"// change code below this line", "const half = (function() {",
"const half = (stats) => ((stats.max + stats.min) / 2.0); // use function argument destructurung", " \"use strict\"; // do not change this line",
"// change code above this line", "",
" // change code below this line",
" return function half(stats) {",
" // use function argument destructuring",
" return (stats.max + stats.min) / 2.0;",
" };",
" // change code above this line",
"",
"})();",
"console.log(stats); // should be object", "console.log(stats); // should be object",
"console.log(half(stats)); // should be 28.015" "console.log(half(stats)); // should be 28.015"
], ],
"tests": [ "tests": [
"// Test stats is an object", "assert(typeof stats === 'object', 'message: <code>stats</code> should be an <code>object</code>.');",
"// Test half is 28.015", "assert(half(stats) === 28.015, 'message: <code>half(stats)</code> should be <code>28.015</code>');",
"// Test destructuring was used" "getUserInput => assert(getUserInput('index').match(/\\(\\s*\\{\\s*\\w+\\s*,\\s*\\w+\\s*\\}\\s*\\)/g), 'message: Destructuring was used.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -580,7 +625,7 @@
"Secondly, the example uses backticks (<code>`</code>), not quotes (<code>'</code> or <code>\"</code>), to wrap the string. Notice that the string is multi-line.", "Secondly, the example uses backticks (<code>`</code>), not quotes (<code>'</code> or <code>\"</code>), to wrap the string. Notice that the string is multi-line.",
"This new way of creating strings gives you more flexibility to create robust strings.", "This new way of creating strings gives you more flexibility to create robust strings.",
"<hr>", "<hr>",
"Use template literal syntax with backticks to display each entry of the <code>result</code> object's <code>failure</code> array. Each entry should be wrapped inside an <code>li</code> element with the class attribute <code>text-warning</code>." "Use template literal syntax with backticks to display each entry of the <code>result</code> object's <code>failure</code> array. Each entry should be wrapped inside an <code>li</code> element with the class attribute <code>text-warning</code>, and listed within the <code>resultDisplayArray</code>."
], ],
"challengeSeed": [ "challengeSeed": [
"const result = {", "const result = {",
@@ -588,21 +633,27 @@
" failure: [\"no-var\", \"var-on-top\", \"linebreak\"],", " failure: [\"no-var\", \"var-on-top\", \"linebreak\"],",
" skipped: [\"id-blacklist\", \"no-dup-keys\"]", " skipped: [\"id-blacklist\", \"no-dup-keys\"]",
"};", "};",
"// change code below this line", "function makeList(arr) {",
"const resultDisplay = null;", " \"use strict\";",
"// change code above this line", "",
"console.log(resultDisplay);", " // change code below this line",
" const resultDisplayArray = null;",
" // change code above this line",
"",
" return resultDisplayArray;",
"}",
"/**", "/**",
" * should look like this", " * makeList(result.failure) should return:",
" * <li class=\"text-warning\">no-var</li>", " * [ <li class=\"text-warning\">no-var</li>,",
" * <li class=\"text-warning\">var-on-top</li>", " * <li class=\"text-warning\">var-on-top</li>, ",
" * <li class=\"text-warning\">linebreak</li>", " * <li class=\"text-warning\">linebreak</li> ]",
" **/" " **/",
"const resultDisplayArray = makeList(result.failure);"
], ],
"tests": [ "tests": [
"// Test resultDisplay is a string", "assert(typeof makeList(result.failure) === 'object' && resultDisplayArray.length === 3, 'message: <code>resultDisplayArray</code> is a list containing <code>result failure</code> messages.');",
"// Test resultDisplay is the desired output", "assert(makeList(result.failure).every((v, i) => v === `<li class=\"text-warning\">${result.failure[i]}</li>`), 'message: <code>resultDisplayArray</code> is the desired output.');",
"// Test template strings were used" "getUserInput => assert(getUserInput('index').match(/\\`<li class=\"text-warning\">\\$\\{\\w+\\}<\\/li>\\`/g), 'message: Template strings were used');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -624,20 +675,21 @@
"Use simple fields with object literals to create and return a <code>Person</code> object." "Use simple fields with object literals to create and return a <code>Person</code> object."
], ],
"challengeSeed": [ "challengeSeed": [
"// change code below this line",
"const createPerson = (name, age, gender) => {", "const createPerson = (name, age, gender) => {",
" \"use strict\";",
" // change code below this line",
" return {", " return {",
" name: name,", " name: name,",
" age: age,", " age: age,",
" gender: gender", " gender: gender",
" };", " };",
" // change code above this line",
"};", "};",
"// change code above this line",
"console.log(createPerson(\"Zodiac Hasbro\", 56, \"male\")); // returns a proper object" "console.log(createPerson(\"Zodiac Hasbro\", 56, \"male\")); // returns a proper object"
], ],
"tests": [ "tests": [
"// Test the output is {name: \"Zodiac Hasbro\", age: 56, gender: \"male\"}", "assert(() => {const res={name:\"Zodiac Hasbro\",age:56,gender:\"male\"}; const person=createPerson(\"Zodiac Hasbro\", 56, \"male\"); return Object.keys(person).every(k => person[k] === res[k]);}, 'message: the output is <code>{name: \"Zodiac Hasbro\", age: 56, gender: \"male\"}</code>.');",
"// Test no : was present" "getUserInput => assert(!getUserInput('index').match(/:/g), 'message: No <code>:</code> were used.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -656,10 +708,12 @@
"Refactor the function <code>setGear</code> inside the object <code>bicycle</code> to use the shorthand syntax described above." "Refactor the function <code>setGear</code> inside the object <code>bicycle</code> to use the shorthand syntax described above."
], ],
"challengeSeed": [ "challengeSeed": [
"// change code below this line", "// change code below this line",
"const bicycle = {", "const bicycle = {",
" gear: 2,", " gear: 2,",
" setGear: function(newGear) {", " setGear: function(newGear) {",
" \"use strict\";",
" this.gear = newGear;", " this.gear = newGear;",
" }", " }",
"};", "};",
@@ -668,8 +722,8 @@
"console.log(bicycle.gear);" "console.log(bicycle.gear);"
], ],
"tests": [ "tests": [
"// Test the output is Sending request to Yanoshi Mimoto", "assert(() => { bicycle.setGear(48); return bicycle.gear === 48 }, 'message: <code>setGear</code> is a function and changes the <code>gear</code> variable.');",
"// Test no : was present" "getUserInput => assert(!getUserInput('index').match(/:\\s*function\\s*\\(\\)/g), 'message: Declarative function was used.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -692,16 +746,21 @@
"The <code>Vegetable</code> lets you create a vegetable object, with a property <code>name</code>, to be passed to constructor." "The <code>Vegetable</code> lets you create a vegetable object, with a property <code>name</code>, to be passed to constructor."
], ],
"challengeSeed": [ "challengeSeed": [
"/* Alter code below this line */", "function makeClass() {",
"const Vegetable = undefined;", " \"use strict\";",
"/* Alter code above this line */", " /* Alter code below this line */",
"",
" /* Alter code above this line */",
" return Vegetable;",
"}",
"const Vegetable = makeClass();",
"const carrot = new Vegetable('carrot');", "const carrot = new Vegetable('carrot');",
"console.log(carrot.name); // => should be 'carrot'" "console.log(carrot.name); // => should be 'carrot'"
], ],
"tests": [ "tests": [
"// Test the Vegetable is a class", "assert(typeof Vegetable === 'function' && typeof Vegetable.constructor === 'function', 'message: <code>Vegetable</code> should be a <code>class</code> with a defined <code>constructor</code> method.');",
"// Test that class keyword was used", "getUserInput => assert(getUserInput('index').match(/class/g),'message: <code>class</code> keyword was used.');",
"// Test that other objects could be created with the class" "assert(() => {const a = new Vegetable(\"apple\"); return typeof a === 'object';},'message: <code>Vegetable</code> can be instantiated.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -722,25 +781,30 @@
"<hr>", "<hr>",
"Use <code>class</code> keyword to create a Thermostat class. The constructor accepts Farenheit temperature.", "Use <code>class</code> keyword to create a Thermostat class. The constructor accepts Farenheit temperature.",
"Now create <code>getter</code> and <code>setter</code> in the class, to obtain the temperature in Celsius scale.", "Now create <code>getter</code> and <code>setter</code> in the class, to obtain the temperature in Celsius scale.",
"Remember that <code>F = C * 9.0 / 5 + 32</code>, where F is the value of temperature in Fahrenheit scale, and C is the value of the same temperature in Celsius scale", "Remember that <code>C = 5/9 * (F - 32)</code> and <code>F = C * 9.0 / 5 + 32</code>, where F is the value of temperature in Fahrenheit scale, and C is the value of the same temperature in Celsius scale",
"Note", "Note",
"When you implement this, you would be tracking the temperature inside the class in one scale - either Fahrenheit or Celsius.", "When you implement this, you would be tracking the temperature inside the class in one scale - either Fahrenheit or Celsius.",
"This is the power of getter or setter - you are creating an API for another user, who would get the correct result, no matter which one you track.", "This is the power of getter or setter - you are creating an API for another user, who would get the correct result, no matter which one you track.",
"In other words, you are abstracting implementation details from the consumer." "In other words, you are abstracting implementation details from the consumer."
], ],
"challengeSeed": [ "challengeSeed": [
"/* Alter code below this line */", "function makeClass() {",
"const Thermostat = undefined;", " \"use strict\";",
"/* Alter code above this line */", " /* Alter code below this line */",
"",
" /* Alter code above this line */",
" return Thermostat;",
"}",
"const Thermostat = makeClass();",
"const thermos = new Thermostat(76); // setting in Farenheit scale", "const thermos = new Thermostat(76); // setting in Farenheit scale",
"let temp = thermos.temperature; // 24.44 in C", "let temp = thermos.temperature; // 24.44 in C",
"thermos.temperature = 26;", "thermos.temperature = 26;",
"temp = thermos.temperature; // 26 in C" "temp = thermos.temperature; // 26 in C"
], ],
"tests": [ "tests": [
"// Test the Thermostat is a class", "assert(typeof Thermostat === 'function' && typeof Thermostat.constructor === 'function','message: <code>Thermostat</code> should be a <code>class</code> with a defined <code>constructor</code> method.');",
"// Test that class keyword was used", "getUserInput => assert(getUserInput('index').match(/class/g),'message: <code>class</code> keyword was used.');",
"// Test that other objects could be created with the class" "assert(() => {const t = new Thermostat(32); return typeof t === 'object' && t.temperature === 0;}, 'message: <code>Thermostat</code> can be instantiated.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -758,16 +822,25 @@
"A description of the above code:", "A description of the above code:",
"<blockquote>import { function } from \"file_path_goes_here\"<br>// We can also import variables the same way!</blockquote>", "<blockquote>import { function } from \"file_path_goes_here\"<br>// We can also import variables the same way!</blockquote>",
"There are a few ways to write an <code>import</code> statement, but the above is a very common use-case.", "There are a few ways to write an <code>import</code> statement, but the above is a very common use-case.",
"<strong>Note</strong><br>the whitespace surrounding the function inside the curly braces is a best practice - it makes it easier to read the <code>import</code> statement.", "<strong>Note</strong><br>The whitespace surrounding the function inside the curly braces is a best practice - it makes it easier to read the <code>import</code> statement.",
"<strong>Note</strong><br>The lessons in this section handle non-browser features. <code>import</code>, and the statements we introduce in the rest of these lessons, won't work on a browser directly, However, we can use various tools to create code out of this to make it work in browser.", "<strong>Note</strong><br>The lessons in this section handle non-browser features. <code>import</code>, and the statements we introduce in the rest of these lessons, won't work on a browser directly. However, we can use various tools to create code out of this to make it work in browser.",
"<strong>Note</strong><br>In most cases, the file path requires a <code>./</code> before it; otherwise, node will look in the <code>node_modules</code> directory first trying to load it as a dependencie.",
"<hr>", "<hr>",
"Add the appropriate <code>import</code> statement that will allow the current file to use the <code>capitalizeString</code> function. The file where this function lives is called <code>\"string_functions\"</code>, and it is in the same directory as the current file." "Add the appropriate <code>import</code> statement that will allow the current file to use the <code>capitalizeString</code> function. The file where this function lives is called <code>\"string_functions\"</code>, and it is in the same directory as the current file."
], ],
"head": [
"window.require = function (str) {",
"if (str === 'string_functions') {",
"return {",
"capitalizeString: str => str.toUpperCase()",
"}}};"
],
"challengeSeed": [ "challengeSeed": [
"\"use strict\";",
"capitalizeString(\"hello!\");" "capitalizeString(\"hello!\");"
], ],
"tests": [ "tests": [
"assert(code.match(/import\\s+\\{\\s?capitalizeString\\s?\\}\\s+from\\s+\"string_functions\"/ig)" "getUserInput => assert(getUserInput('index').match(/import\\s+\\{\\s?capitalizeString\\s?\\}\\s+from\\s+\"string_functions\"/g), 'message: valid <code>import</code> statement');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -787,13 +860,17 @@
"<hr>", "<hr>",
"Below are two variables that I want to make available for other files to use. Utilizing the first way I demonstrated <code>export</code>, export the two variables." "Below are two variables that I want to make available for other files to use. Utilizing the first way I demonstrated <code>export</code>, export the two variables."
], ],
"head": [
"window.exports = function(){};"
],
"challengeSeed": [ "challengeSeed": [
"\"use strict\";",
"const foo = \"bar\";", "const foo = \"bar\";",
"const boo = \"far\";" "const boo = \"far\";"
], ],
"tests": [ "tests": [
"assert(code.match(/export\\s+const\\s+foo\\s+=+\\s\"bar\"/ig))", "getUserInput => assert(getUserInput('index').match(/export\\s+const\\s+foo\\s+=+\\s\"bar\"/g), 'message: <code>foo</code> is exported.');",
"assert(code.match(/export\\s+const\\s+boo\\s+=+\\s\"far\"/ig))" "getUserInput => assert(getUserInput('index').match(/export\\s+const\\s+boo\\s+=+\\s\"far\"/g), 'message: <code>bar</code> is exported.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -809,16 +886,25 @@
"<blockquote>import * as myMathModule from \"math_functions\"<br>myMathModule.add(2,3);<br>myMathModule.subtract(5,3);</blockquote>", "<blockquote>import * as myMathModule from \"math_functions\"<br>myMathModule.add(2,3);<br>myMathModule.subtract(5,3);</blockquote>",
"And breaking down that code:", "And breaking down that code:",
"<blockquote>import * as object_with_name_of_your_choice from \"file_path_goes_here\"<br>object_with_name_of_your_choice.imported_function</blockquote>", "<blockquote>import * as object_with_name_of_your_choice from \"file_path_goes_here\"<br>object_with_name_of_your_choice.imported_function</blockquote>",
"You may use any name following the <code>import *</code> as portion of the statement. In order to utilize this method, it requires an object that receives the imported values. From here, you will use the dot notation to call your imported values.", "You may use any name following the <code>import * as </code>portion of the statement. In order to utilize this method, it requires an object that receives the imported values. From here, you will use the dot notation to call your imported values.",
"<hr>", "<hr>",
"The code below requires the contents of a file, <code>\"capitalize_strings\"</code>, found in the same directory as it, imported. Add the appropriate <code>import *</code> statement to the top of the file, using the object provided." "The code below requires the contents of a file, <code>\"capitalize_strings\"</code>, found in the same directory as it, imported. Add the appropriate <code>import *</code> statement to the top of the file, using the object provided."
], ],
"head": [
"window.require = function(str) {",
"if (str === 'capitalize_strings') {",
"return {",
"capitalize: str => str.toUpperCase(),",
"lowercase: str => str.toLowerCase()",
"}}};"
],
"challengeSeed": [ "challengeSeed": [
"\"use strict\";",
"myStringModule.capitalize(\"foo\");", "myStringModule.capitalize(\"foo\");",
"myStringModule.lowercase(\"Foo\");" "myStringModule.lowercase(\"Foo\");"
], ],
"tests": [ "tests": [
"assert(code.match(/import\\s+\\*\\s+as\\s+myStringModule\\s+from\\s+\"capitalize_strings\"/ig))" "getUserInput => assert(getUserInput('index').match(/import\\s+\\*\\s+as\\s+myStringModule\\s+from\\s+\"capitalize_strings\"/g), 'message: Properly uses <code>import * as</code> syntax.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -837,11 +923,15 @@
"<hr>", "<hr>",
"The following function should be the fallback value for the module. Please add the necessary code to do so." "The following function should be the fallback value for the module. Please add the necessary code to do so."
], ],
"head": [
"window.exports = function(){};"
],
"challengeSeed": [ "challengeSeed": [
"\"use strict\";",
"function subtract(x,y) {return x - y;}" "function subtract(x,y) {return x - y;}"
], ],
"tests": [ "tests": [
"assert(code.match(/export\\s+default\\s+function\\s+subtract\\(x,y\\)\\s+{return\\s+x\\s-\\s+y;}/ig))" "getUserInput => assert(getUserInput('index').match(/export\\s+default\\s+function\\s+subtract\\(x,y\\)\\s+{return\\s+x\\s-\\s+y;}/g), 'message: Proper used of <code>export</code> fallback.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",
@@ -859,11 +949,19 @@
"<hr>", "<hr>",
"In the following code, please import the default export, <code>subtract</code>, from the file <code>\"math_functions\"</code>, found in the same directory as this file." "In the following code, please import the default export, <code>subtract</code>, from the file <code>\"math_functions\"</code>, found in the same directory as this file."
], ],
"head": [
"window.require = function(str) {",
"if (str === 'math_functions') {",
"return function(a, b) {",
"return a - b;",
"}}};"
],
"challengeSeed": [ "challengeSeed": [
"\"use strict\";",
"subtract(7,4);" "subtract(7,4);"
], ],
"tests": [ "tests": [
"assert(code.match(/import\\s+subtract\\s+from\\s+\"math_functions\"/ig))" "getUserInput => assert(getUserInput('index').match(/import\\s+subtract\\s+from\\s+\"math_functions\"/g), 'message: Properly imports <code>export default</code> method.');"
], ],
"type": "waypoint", "type": "waypoint",
"releasedOn": "Feb 17, 2017", "releasedOn": "Feb 17, 2017",

View File

@@ -125,77 +125,6 @@
} }
} }
}, },
{
"id": "aaa48de84e1ecc7c742e1124",
"title": "Check for Palindromes",
"description": [
"Return <code>true</code> if the given string is a palindrome. Otherwise, return <code>false</code>.",
"A <dfn>palindrome</dfn> is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.",
"<strong>Note</strong><br>You'll need to remove <strong>all non-alphanumeric characters</strong> (punctuation, spaces and symbols) and turn everything into the same case (lower or upper case) in order to check for palindromes.",
"We'll pass strings with varying formats, such as <code>\"racecar\"</code>, <code>\"RaceCar\"</code>, and <code>\"race CAR\"</code> among others.",
"We'll also pass strings with special symbols, such as <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code>, and <code>\"2_A3*3#A2\"</code>.",
"Remember to use <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Read-Search-Ask</a> if you get stuck. Write your own code."
],
"challengeSeed": [
"function palindrome(str) {",
" // Good luck!",
" return true;",
"}",
"",
"",
"",
"palindrome(\"eye\");"
],
"tests": [
"assert(typeof palindrome(\"eye\") === \"boolean\", 'message: <code>palindrome(\"eye\")</code> should return a boolean.');",
"assert(palindrome(\"eye\") === true, 'message: <code>palindrome(\"eye\")</code> should return true.');",
"assert(palindrome(\"_eye\") === true, 'message: <code>palindrome(\"_eye\")</code> should return true.');",
"assert(palindrome(\"race car\") === true, 'message: <code>palindrome(\"race car\")</code> should return true.');",
"assert(palindrome(\"not a palindrome\") === false, 'message: <code>palindrome(\"not a palindrome\")</code> should return false.');",
"assert(palindrome(\"A man, a plan, a canal. Panama\") === true, 'message: <code>palindrome(\"A man, a plan, a canal. Panama\")</code> should return true.');",
"assert(palindrome(\"never odd or even\") === true, 'message: <code>palindrome(\"never odd or even\")</code> should return true.');",
"assert(palindrome(\"nope\") === false, 'message: <code>palindrome(\"nope\")</code> should return false.');",
"assert(palindrome(\"almostomla\") === false, 'message: <code>palindrome(\"almostomla\")</code> should return false.');",
"assert(palindrome(\"My age is 0, 0 si ega ym.\") === true, 'message: <code>palindrome(\"My age is 0, 0 si ega ym.\")</code> should return true.');",
"assert(palindrome(\"1 eye for of 1 eye.\") === false, 'message: <code>palindrome(\"1 eye for of 1 eye.\")</code> should return false.');",
"assert(palindrome(\"0_0 (: /-\\ :) 0-0\") === true, 'message: <code>palindrome(\"0_0 (: /-\\ :) 0-0\")</code> should return true.');",
"assert(palindrome(\"five|\\_/|four\") === false, 'message: <code>palindrome(\"five|\\_/|four\")</code> should return false.');"
],
"type": "bonfire",
"isRequired": true,
"solutions": [
"function palindrome(str) {\n var string = str.toLowerCase().split(/[^A-Za-z0-9]/gi).join('');\n var aux = string.split('');\n if (aux.join('') === aux.reverse().join('')){\n return true;\n }\n\n return false;\n}"
],
"MDNlinks": [
"String.prototype.replace()",
"String.prototype.toLowerCase()"
],
"challengeType": 5,
"translations": {
"es": {
"title": "Verifica si es palíndromo",
"description": [
"Crea una función que devuelva <code>true</code> si una cadena de texto dada es un palíndromo, y que devuelva <code>false</code> en caso contrario",
"Un palíndromo es una palabra u oración que se escribe de la misma forma en ambos sentidos, sin tomar en cuenta signos de puntuación, espacios y sin distinguir entre mayúsculas y minúsculas.",
"Tendrás que quitar los caracteres no alfanuméricos (signos de puntuación, espacioes y símbolos) y transformar las letras a minúsculas para poder verificar si el texto es palíndromo.",
"Te proveeremos textos en varios formatos, como \"racecar\", \"RaceCar\", and \"race CAR\" entre otros.",
"También vamos a pasar cadenas con símbolos especiales, tales como <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code>, y <code>\"2_A3*3#A2\"</code>.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"pt-br": {
"title": "Procure por Palíndromos",
"description": [
"Retorne <code>true</code> se o texto fornecida é um palíndromo. Caso contrário, retorne <code>false</code>.",
"Um <dfn>palíndromo</dfn> é uma palavra ou sentença que é soletrada da mesma maneira tanto para a frente quanto para trás, ignorando pontuação, maiúsculas e minúsculas, e espaçamento.",
"<strong>Nota</strong><br>Você precisará remover <strong>todos caracteres não alfanuméricos</strong> (pontuação, espaços e símbolos) e transformar todas as letras em maiúsculas ou minúsculas para procurar por palíndromos.",
"Nós vamos passar textos de vários formatos, tais como <code>\"racecar\"</code>, <code>\"RaceCar\"</code> e <code>\"race CAR\"</code> entre outras.",
"Nós também vamos passar textos com símbolos especiais, tais como <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code> e <code>\"2_A3*3#A2\"</code>.",
"Lembre-se de usar <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Ler-Pesquisar-Perguntar</a> se você ficar travado. Escreva seu próprio código."
]
}
}
},
{ {
"id": "a39963a4c10bc8b4d4f06d7e", "id": "a39963a4c10bc8b4d4f06d7e",
"title": "Seek and Destroy", "title": "Seek and Destroy",
@@ -357,142 +286,6 @@
} }
} }
}, },
{
"id": "a7f4d8f2483413a6ce226cac",
"title": "Roman Numeral Converter",
"description": [
"Convert the given number into a roman numeral.",
"All <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">roman numerals</a> answers should be provided in upper-case.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
"function convertToRoman(num) {",
" return num;",
"}",
"",
"convertToRoman(36);"
],
"solutions": [
"function convertToRoman(num) {\n var ref = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1]];\n var res = [];\n ref.forEach(function(p) {\n while (num >= p[1]) {\n res.push(p[0]);\n num -= p[1];\n }\n });\n return res.join('');\n}"
],
"tests": [
"assert.deepEqual(convertToRoman(2), \"II\", 'message: <code>convertToRoman(2)</code> should return \"II\".');",
"assert.deepEqual(convertToRoman(3), \"III\", 'message: <code>convertToRoman(3)</code> should return \"III\".');",
"assert.deepEqual(convertToRoman(4), \"IV\", 'message: <code>convertToRoman(4)</code> should return \"IV\".');",
"assert.deepEqual(convertToRoman(5), \"V\", 'message: <code>convertToRoman(5)</code> should return \"V\".');",
"assert.deepEqual(convertToRoman(9), \"IX\", 'message: <code>convertToRoman(9)</code> should return \"IX\".');",
"assert.deepEqual(convertToRoman(12), \"XII\", 'message: <code>convertToRoman(12)</code> should return \"XII\".');",
"assert.deepEqual(convertToRoman(16), \"XVI\", 'message: <code>convertToRoman(16)</code> should return \"XVI\".');",
"assert.deepEqual(convertToRoman(29), \"XXIX\", 'message: <code>convertToRoman(29)</code> should return \"XXIX\".');",
"assert.deepEqual(convertToRoman(44), \"XLIV\", 'message: <code>convertToRoman(44)</code> should return \"XLIV\".');",
"assert.deepEqual(convertToRoman(45), \"XLV\", 'message: <code>convertToRoman(45)</code> should return \"XLV\"');",
"assert.deepEqual(convertToRoman(68), \"LXVIII\", 'message: <code>convertToRoman(68)</code> should return \"LXVIII\"');",
"assert.deepEqual(convertToRoman(83), \"LXXXIII\", 'message: <code>convertToRoman(83)</code> should return \"LXXXIII\"');",
"assert.deepEqual(convertToRoman(97), \"XCVII\", 'message: <code>convertToRoman(97)</code> should return \"XCVII\"');",
"assert.deepEqual(convertToRoman(99), \"XCIX\", 'message: <code>convertToRoman(99)</code> should return \"XCIX\"');",
"assert.deepEqual(convertToRoman(400), \"CD\", 'message: <code>convertToRoman(400)</code> should return \"CD\"');",
"assert.deepEqual(convertToRoman(500), \"D\", 'message: <code>convertToRoman(500)</code> should return \"D\"');",
"assert.deepEqual(convertToRoman(501), \"DI\", 'message: <code>convertToRoman(501)</code> should return \"DI\"');",
"assert.deepEqual(convertToRoman(649), \"DCXLIX\", 'message: <code>convertToRoman(649)</code> should return \"DCXLIX\"');",
"assert.deepEqual(convertToRoman(798), \"DCCXCVIII\", 'message: <code>convertToRoman(798)</code> should return \"DCCXCVIII\"');",
"assert.deepEqual(convertToRoman(891), \"DCCCXCI\", 'message: <code>convertToRoman(891)</code> should return \"DCCCXCI\"');",
"assert.deepEqual(convertToRoman(1000), \"M\", 'message: <code>convertToRoman(1000)</code> should return \"M\"');",
"assert.deepEqual(convertToRoman(1004), \"MIV\", 'message: <code>convertToRoman(1004)</code> should return \"MIV\"');",
"assert.deepEqual(convertToRoman(1006), \"MVI\", 'message: <code>convertToRoman(1006)</code> should return \"MVI\"');",
"assert.deepEqual(convertToRoman(1023), \"MXXIII\", 'message: <code>convertToRoman(1023)</code> should return \"MXXIII\"');",
"assert.deepEqual(convertToRoman(2014), \"MMXIV\", 'message: <code>convertToRoman(2014)</code> should return \"MMXIV\"');",
"assert.deepEqual(convertToRoman(3999), \"MMMCMXCIX\", 'message: <code>convertToRoman(3999)</code> should return \"MMMCMXCIX\"');"
],
"type": "bonfire",
"MDNlinks": [
"Roman Numerals",
"Array.prototype.splice()",
"Array.prototype.indexOf()",
"Array.prototype.join()"
],
"isRequired": true,
"challengeType": 5,
"translations": {
"es": {
"title": "Convertior de números romanos",
"description": [
"Convierte el número dado en numeral romano.",
"Todos los <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">numerales romanos</a> en las respuestas deben estar en mayúsculas.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"fr": {
"title": "Convertir en chiffres romains",
"description": [
"Convertis le nombre donné en chiffres romains.",
"Tous les <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">chiffres romains</a> doivent être en lettres capitales.",
"N'oublie pas d'utiliser <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Lire-Chercher-Demander</a> si tu es bloqué. Essaye de trouver un partenaire. Écris ton propre code."
]
}
}
},
{
"id": "56533eb9ac21ba0edf2244e2",
"title": "Caesars Cipher",
"description": [
"One of the simplest and most widely known <dfn>ciphers</dfn> is a <code>Caesar cipher</code>, also known as a <code>shift cipher</code>. In a <code>shift cipher</code> the meanings of the letters are shifted by some set amount.",
"A common modern use is the <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> cipher, where the values of the letters are shifted by 13 places. Thus 'A' &harr; 'N', 'B' &harr; 'O' and so on.",
"Write a function which takes a <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> encoded string as input and returns a decoded string.",
"All letters will be uppercase. Do not transform any non-alphabetic character (i.e. spaces, punctuation), but do pass them on.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
"function rot13(str) { // LBH QVQ VG!",
" ",
" return str;",
"}",
"",
"// Change the inputs below to test",
"rot13(\"SERR PBQR PNZC\");"
],
"tail": [
""
],
"solutions": [
"var lookup = {\n 'A': 'N','B': 'O','C': 'P','D': 'Q',\n 'E': 'R','F': 'S','G': 'T','H': 'U',\n 'I': 'V','J': 'W','K': 'X','L': 'Y',\n 'M': 'Z','N': 'A','O': 'B','P': 'C',\n 'Q': 'D','R': 'E','S': 'F','T': 'G',\n 'U': 'H','V': 'I','W': 'J','X': 'K',\n 'Y': 'L','Z': 'M' \n};\n\nfunction rot13(encodedStr) {\n var codeArr = encodedStr.split(\"\"); // String to Array\n var decodedArr = []; // Your Result goes here\n // Only change code below this line\n \n decodedArr = codeArr.map(function(letter) {\n if(lookup.hasOwnProperty(letter)) {\n letter = lookup[letter];\n }\n return letter;\n });\n\n // Only change code above this line\n return decodedArr.join(\"\"); // Array to String\n}"
],
"tests": [
"assert(rot13(\"SERR PBQR PNZC\") === \"FREE CODE CAMP\", 'message: <code>rot13(\"SERR PBQR PNZC\")</code> should decode to <code>FREE CODE CAMP</code>');",
"assert(rot13(\"SERR CVMMN!\") === \"FREE PIZZA!\", 'message: <code>rot13(\"SERR CVMMN!\")</code> should decode to <code>FREE PIZZA!</code>');",
"assert(rot13(\"SERR YBIR?\") === \"FREE LOVE?\", 'message: <code>rot13(\"SERR YBIR?\")</code> should decode to <code>FREE LOVE?</code>');",
"assert(rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\") === \"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.\", 'message: <code>rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\")</code> should decode to <code>THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.</code>');"
],
"type": "bonfire",
"MDNlinks": [
"String.prototype.charCodeAt()",
"String.fromCharCode()"
],
"challengeType": 5,
"isRequired": true,
"releasedOn": "January 1, 2016",
"translations": {
"es": {
"title": "Cifrado César",
"description": [
"Uno de los <dfn>cifrados</dfn> más simples y ampliamente conocidos es el <code>cifrado César</code>, también llamado <code>cifrado por desplazamiento</code>. En un <code>cifrado por desplazamiento</code> los significados de las letras se desplazan por una cierta cantidad.",
"Un uso moderno común es el cifrado <a href=\"https://es.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> , donde los valores de las letras se desplazan 13 espacios. De esta forma 'A' &harr; 'N', 'B' &harr; 'O' y así.",
"Crea una función que tome una cadena de texto cifrada en <a href=\"https://es.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> como argumento y que devuelva la cadena de texto decodificada.",
"Todas las letras que se te pasen van a estar en mayúsculas. No transformes ningún caracter no-alfabético (por ejemplo: espacios, puntuación). Simplemente pásalos intactos.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"pt-br": {
"title": "Cifra de César",
"description": [
"Uma das mais simples e mais conhecidas <dfn>cifras</dfn> é a <code>cifra de César</code>, também conhecida como <code>cifra de troca</code>. Em uma <code>cifra de troca</code> os significados das letras são deslocados por um determinado valor.",
"Um uso moderno comum é a cifra <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a>, aonde os valores das letras são deslocados por 13 lugares. Logo 'A' &harr; 'N', 'B' &harr; 'O' e assim por diante.",
"Escreva uma função que recebe um texto criptografado com <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> como entrada e retorna o texto desencriptado.",
"Todas as letras serão maiúsculas. Não transforme nenhum caracter não alfanuméricos (como espaços, pontuação), mas passe-os adiante.",
"Lembre-se de usar <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Ler-Pesquisar-Perguntar</a> se você ficar travado. Escreva seu próprio código."
]
}
}
},
{ {
"id": "aa7697ea2477d1316795783b", "id": "aa7697ea2477d1316795783b",
"title": "Pig Latin", "title": "Pig Latin",

View File

@@ -0,0 +1,389 @@
{
"name": "JavaScript Algorithms and Data Structures Projects",
"order": 8,
"time": "50 hours",
"helpRoom": "HelpJavaScript",
"challenges": [
{
"id": "aaa48de84e1ecc7c742e1124",
"title": "Palindrome Checker",
"description": [
"Return <code>true</code> if the given string is a palindrome. Otherwise, return <code>false</code>.",
"A <dfn>palindrome</dfn> is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.",
"<strong>Note</strong><br>You'll need to remove <strong>all non-alphanumeric characters</strong> (punctuation, spaces and symbols) and turn everything into the same case (lower or upper case) in order to check for palindromes.",
"We'll pass strings with varying formats, such as <code>\"racecar\"</code>, <code>\"RaceCar\"</code>, and <code>\"race CAR\"</code> among others.",
"We'll also pass strings with special symbols, such as <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code>, and <code>\"2_A3*3#A2\"</code>.",
"Remember to use <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Read-Search-Ask</a> if you get stuck. Write your own code."
],
"challengeSeed": [
"function palindrome(str) {",
" // Good luck!",
" return true;",
"}",
"",
"",
"",
"palindrome(\"eye\");"
],
"tests": [
"assert(typeof palindrome(\"eye\") === \"boolean\", 'message: <code>palindrome(\"eye\")</code> should return a boolean.');",
"assert(palindrome(\"eye\") === true, 'message: <code>palindrome(\"eye\")</code> should return true.');",
"assert(palindrome(\"_eye\") === true, 'message: <code>palindrome(\"_eye\")</code> should return true.');",
"assert(palindrome(\"race car\") === true, 'message: <code>palindrome(\"race car\")</code> should return true.');",
"assert(palindrome(\"not a palindrome\") === false, 'message: <code>palindrome(\"not a palindrome\")</code> should return false.');",
"assert(palindrome(\"A man, a plan, a canal. Panama\") === true, 'message: <code>palindrome(\"A man, a plan, a canal. Panama\")</code> should return true.');",
"assert(palindrome(\"never odd or even\") === true, 'message: <code>palindrome(\"never odd or even\")</code> should return true.');",
"assert(palindrome(\"nope\") === false, 'message: <code>palindrome(\"nope\")</code> should return false.');",
"assert(palindrome(\"almostomla\") === false, 'message: <code>palindrome(\"almostomla\")</code> should return false.');",
"assert(palindrome(\"My age is 0, 0 si ega ym.\") === true, 'message: <code>palindrome(\"My age is 0, 0 si ega ym.\")</code> should return true.');",
"assert(palindrome(\"1 eye for of 1 eye.\") === false, 'message: <code>palindrome(\"1 eye for of 1 eye.\")</code> should return false.');",
"assert(palindrome(\"0_0 (: /-\\ :) 0-0\") === true, 'message: <code>palindrome(\"0_0 (: /-\\ :) 0-0\")</code> should return true.');",
"assert(palindrome(\"five|\\_/|four\") === false, 'message: <code>palindrome(\"five|\\_/|four\")</code> should return false.');"
],
"type": "bonfire",
"isRequired": true,
"solutions": [
"function palindrome(str) {\n var string = str.toLowerCase().split(/[^A-Za-z0-9]/gi).join('');\n var aux = string.split('');\n if (aux.join('') === aux.reverse().join('')){\n return true;\n }\n\n return false;\n}"
],
"MDNlinks": [
"String.prototype.replace()",
"String.prototype.toLowerCase()"
],
"challengeType": 5,
"translations": {
"es": {
"title": "Verifica si es palíndromo",
"description": [
"Crea una función que devuelva <code>true</code> si una cadena de texto dada es un palíndromo, y que devuelva <code>false</code> en caso contrario",
"Un palíndromo es una palabra u oración que se escribe de la misma forma en ambos sentidos, sin tomar en cuenta signos de puntuación, espacios y sin distinguir entre mayúsculas y minúsculas.",
"Tendrás que quitar los caracteres no alfanuméricos (signos de puntuación, espacioes y símbolos) y transformar las letras a minúsculas para poder verificar si el texto es palíndromo.",
"Te proveeremos textos en varios formatos, como \"racecar\", \"RaceCar\", and \"race CAR\" entre otros.",
"También vamos a pasar cadenas con símbolos especiales, tales como <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code>, y <code>\"2_A3*3#A2\"</code>.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"pt-br": {
"title": "Procure por Palíndromos",
"description": [
"Retorne <code>true</code> se o texto fornecida é um palíndromo. Caso contrário, retorne <code>false</code>.",
"Um <dfn>palíndromo</dfn> é uma palavra ou sentença que é soletrada da mesma maneira tanto para a frente quanto para trás, ignorando pontuação, maiúsculas e minúsculas, e espaçamento.",
"<strong>Nota</strong><br>Você precisará remover <strong>todos caracteres não alfanuméricos</strong> (pontuação, espaços e símbolos) e transformar todas as letras em maiúsculas ou minúsculas para procurar por palíndromos.",
"Nós vamos passar textos de vários formatos, tais como <code>\"racecar\"</code>, <code>\"RaceCar\"</code> e <code>\"race CAR\"</code> entre outras.",
"Nós também vamos passar textos com símbolos especiais, tais como <code>\"2A3*3a2\"</code>, <code>\"2A3 3a2\"</code> e <code>\"2_A3*3#A2\"</code>.",
"Lembre-se de usar <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Ler-Pesquisar-Perguntar</a> se você ficar travado. Escreva seu próprio código."
]
}
}
},
{
"id": "a7f4d8f2483413a6ce226cac",
"title": "Roman Numeral Converter",
"description": [
"Convert the given number into a roman numeral.",
"All <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">roman numerals</a> answers should be provided in upper-case.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
"function convertToRoman(num) {",
" return num;",
"}",
"",
"convertToRoman(36);"
],
"solutions": [
"function convertToRoman(num) {\n var ref = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1]];\n var res = [];\n ref.forEach(function(p) {\n while (num >= p[1]) {\n res.push(p[0]);\n num -= p[1];\n }\n });\n return res.join('');\n}"
],
"tests": [
"assert.deepEqual(convertToRoman(2), \"II\", 'message: <code>convertToRoman(2)</code> should return \"II\".');",
"assert.deepEqual(convertToRoman(3), \"III\", 'message: <code>convertToRoman(3)</code> should return \"III\".');",
"assert.deepEqual(convertToRoman(4), \"IV\", 'message: <code>convertToRoman(4)</code> should return \"IV\".');",
"assert.deepEqual(convertToRoman(5), \"V\", 'message: <code>convertToRoman(5)</code> should return \"V\".');",
"assert.deepEqual(convertToRoman(9), \"IX\", 'message: <code>convertToRoman(9)</code> should return \"IX\".');",
"assert.deepEqual(convertToRoman(12), \"XII\", 'message: <code>convertToRoman(12)</code> should return \"XII\".');",
"assert.deepEqual(convertToRoman(16), \"XVI\", 'message: <code>convertToRoman(16)</code> should return \"XVI\".');",
"assert.deepEqual(convertToRoman(29), \"XXIX\", 'message: <code>convertToRoman(29)</code> should return \"XXIX\".');",
"assert.deepEqual(convertToRoman(44), \"XLIV\", 'message: <code>convertToRoman(44)</code> should return \"XLIV\".');",
"assert.deepEqual(convertToRoman(45), \"XLV\", 'message: <code>convertToRoman(45)</code> should return \"XLV\"');",
"assert.deepEqual(convertToRoman(68), \"LXVIII\", 'message: <code>convertToRoman(68)</code> should return \"LXVIII\"');",
"assert.deepEqual(convertToRoman(83), \"LXXXIII\", 'message: <code>convertToRoman(83)</code> should return \"LXXXIII\"');",
"assert.deepEqual(convertToRoman(97), \"XCVII\", 'message: <code>convertToRoman(97)</code> should return \"XCVII\"');",
"assert.deepEqual(convertToRoman(99), \"XCIX\", 'message: <code>convertToRoman(99)</code> should return \"XCIX\"');",
"assert.deepEqual(convertToRoman(400), \"CD\", 'message: <code>convertToRoman(400)</code> should return \"CD\"');",
"assert.deepEqual(convertToRoman(500), \"D\", 'message: <code>convertToRoman(500)</code> should return \"D\"');",
"assert.deepEqual(convertToRoman(501), \"DI\", 'message: <code>convertToRoman(501)</code> should return \"DI\"');",
"assert.deepEqual(convertToRoman(649), \"DCXLIX\", 'message: <code>convertToRoman(649)</code> should return \"DCXLIX\"');",
"assert.deepEqual(convertToRoman(798), \"DCCXCVIII\", 'message: <code>convertToRoman(798)</code> should return \"DCCXCVIII\"');",
"assert.deepEqual(convertToRoman(891), \"DCCCXCI\", 'message: <code>convertToRoman(891)</code> should return \"DCCCXCI\"');",
"assert.deepEqual(convertToRoman(1000), \"M\", 'message: <code>convertToRoman(1000)</code> should return \"M\"');",
"assert.deepEqual(convertToRoman(1004), \"MIV\", 'message: <code>convertToRoman(1004)</code> should return \"MIV\"');",
"assert.deepEqual(convertToRoman(1006), \"MVI\", 'message: <code>convertToRoman(1006)</code> should return \"MVI\"');",
"assert.deepEqual(convertToRoman(1023), \"MXXIII\", 'message: <code>convertToRoman(1023)</code> should return \"MXXIII\"');",
"assert.deepEqual(convertToRoman(2014), \"MMXIV\", 'message: <code>convertToRoman(2014)</code> should return \"MMXIV\"');",
"assert.deepEqual(convertToRoman(3999), \"MMMCMXCIX\", 'message: <code>convertToRoman(3999)</code> should return \"MMMCMXCIX\"');"
],
"type": "bonfire",
"MDNlinks": [
"Roman Numerals",
"Array.prototype.splice()",
"Array.prototype.indexOf()",
"Array.prototype.join()"
],
"isRequired": true,
"challengeType": 5,
"translations": {
"es": {
"title": "Convertior de números romanos",
"description": [
"Convierte el número dado en numeral romano.",
"Todos los <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">numerales romanos</a> en las respuestas deben estar en mayúsculas.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"fr": {
"title": "Convertir en chiffres romains",
"description": [
"Convertis le nombre donné en chiffres romains.",
"Tous les <a href=\"http://www.mathsisfun.com/roman-numerals.html\" target=\"_blank\">chiffres romains</a> doivent être en lettres capitales.",
"N'oublie pas d'utiliser <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Lire-Chercher-Demander</a> si tu es bloqué. Essaye de trouver un partenaire. Écris ton propre code."
]
}
}
},
{
"id": "56533eb9ac21ba0edf2244e2",
"title": "Caesars Cipher",
"description": [
"One of the simplest and most widely known <dfn>ciphers</dfn> is a <code>Caesar cipher</code>, also known as a <code>shift cipher</code>. In a <code>shift cipher</code> the meanings of the letters are shifted by some set amount.",
"A common modern use is the <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> cipher, where the values of the letters are shifted by 13 places. Thus 'A' &harr; 'N', 'B' &harr; 'O' and so on.",
"Write a function which takes a <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> encoded string as input and returns a decoded string.",
"All letters will be uppercase. Do not transform any non-alphabetic character (i.e. spaces, punctuation), but do pass them on.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
"function rot13(str) { // LBH QVQ VG!",
" ",
" return str;",
"}",
"",
"// Change the inputs below to test",
"rot13(\"SERR PBQR PNZC\");"
],
"tail": [
""
],
"solutions": [
"var lookup = {\n 'A': 'N','B': 'O','C': 'P','D': 'Q',\n 'E': 'R','F': 'S','G': 'T','H': 'U',\n 'I': 'V','J': 'W','K': 'X','L': 'Y',\n 'M': 'Z','N': 'A','O': 'B','P': 'C',\n 'Q': 'D','R': 'E','S': 'F','T': 'G',\n 'U': 'H','V': 'I','W': 'J','X': 'K',\n 'Y': 'L','Z': 'M' \n};\n\nfunction rot13(encodedStr) {\n var codeArr = encodedStr.split(\"\"); // String to Array\n var decodedArr = []; // Your Result goes here\n // Only change code below this line\n \n decodedArr = codeArr.map(function(letter) {\n if(lookup.hasOwnProperty(letter)) {\n letter = lookup[letter];\n }\n return letter;\n });\n\n // Only change code above this line\n return decodedArr.join(\"\"); // Array to String\n}"
],
"tests": [
"assert(rot13(\"SERR PBQR PNZC\") === \"FREE CODE CAMP\", 'message: <code>rot13(\"SERR PBQR PNZC\")</code> should decode to <code>FREE CODE CAMP</code>');",
"assert(rot13(\"SERR CVMMN!\") === \"FREE PIZZA!\", 'message: <code>rot13(\"SERR CVMMN!\")</code> should decode to <code>FREE PIZZA!</code>');",
"assert(rot13(\"SERR YBIR?\") === \"FREE LOVE?\", 'message: <code>rot13(\"SERR YBIR?\")</code> should decode to <code>FREE LOVE?</code>');",
"assert(rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\") === \"THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.\", 'message: <code>rot13(\"GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.\")</code> should decode to <code>THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.</code>');"
],
"type": "bonfire",
"MDNlinks": [
"String.prototype.charCodeAt()",
"String.fromCharCode()"
],
"challengeType": 5,
"isRequired": true,
"releasedOn": "January 1, 2016",
"translations": {
"es": {
"title": "Cifrado César",
"description": [
"Uno de los <dfn>cifrados</dfn> más simples y ampliamente conocidos es el <code>cifrado César</code>, también llamado <code>cifrado por desplazamiento</code>. En un <code>cifrado por desplazamiento</code> los significados de las letras se desplazan por una cierta cantidad.",
"Un uso moderno común es el cifrado <a href=\"https://es.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> , donde los valores de las letras se desplazan 13 espacios. De esta forma 'A' &harr; 'N', 'B' &harr; 'O' y así.",
"Crea una función que tome una cadena de texto cifrada en <a href=\"https://es.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> como argumento y que devuelva la cadena de texto decodificada.",
"Todas las letras que se te pasen van a estar en mayúsculas. No transformes ningún caracter no-alfabético (por ejemplo: espacios, puntuación). Simplemente pásalos intactos.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leer-Buscar-Preguntar</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"pt-br": {
"title": "Cifra de César",
"description": [
"Uma das mais simples e mais conhecidas <dfn>cifras</dfn> é a <code>cifra de César</code>, também conhecida como <code>cifra de troca</code>. Em uma <code>cifra de troca</code> os significados das letras são deslocados por um determinado valor.",
"Um uso moderno comum é a cifra <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a>, aonde os valores das letras são deslocados por 13 lugares. Logo 'A' &harr; 'N', 'B' &harr; 'O' e assim por diante.",
"Escreva uma função que recebe um texto criptografado com <a href=\"https://en.wikipedia.org/wiki/ROT13\" target='_blank'>ROT13</a> como entrada e retorna o texto desencriptado.",
"Todas as letras serão maiúsculas. Não transforme nenhum caracter não alfanuméricos (como espaços, pontuação), mas passe-os adiante.",
"Lembre-se de usar <a href=\"http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514\" target=\"_blank\">Ler-Pesquisar-Perguntar</a> se você ficar travado. Escreva seu próprio código."
]
}
}
},
{
"id": "aff0395860f5d3034dc0bfc9",
"title": "Telephone Number Validator",
"description": [
"Return <code>true</code> if the passed string looks like a valid US phone number.",
"The user may fill out the form field any way they choose as long as it has the format of a valid US number. The following are examples of valid formats for US numbers (refer to the tests below for other variants):",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"For this challenge you will be presented with a string such as <code>800-692-7753</code> or <code>8oo-six427676;laskdjf</code>. Your job is to validate or reject the US phone number based on any combination of the formats provided above. The area code is required. If the country code is provided, you must confirm that the country code is <code>1</code>. Return <code>true</code> if the string is a valid US phone number; otherwise return <code>false</code>.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
"function telephoneCheck(str) {",
" // Good luck!",
" return true;",
"}",
"",
"telephoneCheck(\"555-555-5555\");"
],
"solutions": [
"var re = /^([+]?1[\\s]?)?((?:[(](?:[2-9]1[02-9]|[2-9][02-8][0-9])[)][\\s]?)|(?:(?:[2-9]1[02-9]|[2-9][02-8][0-9])[\\s.-]?)){1}([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2}[\\s.-]?){1}([0-9]{4}){1}$/;\n\nfunction telephoneCheck(str) {\n return re.test(str);\n}\n\ntelephoneCheck(\"555-555-5555\");"
],
"tests": [
"assert(typeof telephoneCheck(\"555-555-5555\") === \"boolean\", 'message: <code>telephoneCheck(\"555-555-5555\")</code> should return a boolean.');",
"assert(telephoneCheck(\"1 555-555-5555\") === true, 'message: <code>telephoneCheck(\"1 555-555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"1 (555) 555-5555\") === true, 'message: <code>telephoneCheck(\"1 (555) 555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"5555555555\") === true, 'message: <code>telephoneCheck(\"5555555555\")</code> should return true.');",
"assert(telephoneCheck(\"555-555-5555\") === true, 'message: <code>telephoneCheck(\"555-555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"(555)555-5555\") === true, 'message: <code>telephoneCheck(\"(555)555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"1(555)555-5555\") === true, 'message: <code>telephoneCheck(\"1(555)555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"555-5555\") === false, 'message: <code>telephoneCheck(\"555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"5555555\") === false, 'message: <code>telephoneCheck(\"5555555\")</code> should return false.');",
"assert(telephoneCheck(\"1 555)555-5555\") === false, 'message: <code>telephoneCheck(\"1 555)555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"1 555 555 5555\") === true, 'message: <code>telephoneCheck(\"1 555 555 5555\")</code> should return true.');",
"assert(telephoneCheck(\"1 456 789 4444\") === true, 'message: <code>telephoneCheck(\"1 456 789 4444\")</code> should return true.');",
"assert(telephoneCheck(\"123**&!!asdf#\") === false, 'message: <code>telephoneCheck(\"123**&!!asdf#\")</code> should return false.');",
"assert(telephoneCheck(\"55555555\") === false, 'message: <code>telephoneCheck(\"55555555\")</code> should return false.');",
"assert(telephoneCheck(\"(6054756961)\") === false, 'message: <code>telephoneCheck(\"(6054756961)\")</code> should return false');",
"assert(telephoneCheck(\"2 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"2 (757) 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"0 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"0 (757) 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"-1 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"-1 (757) 622-7382\")</code> should return false');",
"assert(telephoneCheck(\"2 757 622-7382\") === false, 'message: <code>telephoneCheck(\"2 757 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"10 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"10 (757) 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"27576227382\") === false, 'message: <code>telephoneCheck(\"27576227382\")</code> should return false.');",
"assert(telephoneCheck(\"(275)76227382\") === false, 'message: <code>telephoneCheck(\"(275)76227382\")</code> should return false.');",
"assert(telephoneCheck(\"2(757)6227382\") === false, 'message: <code>telephoneCheck(\"2(757)6227382\")</code> should return false.');",
"assert(telephoneCheck(\"2(757)622-7382\") === false, 'message: <code>telephoneCheck(\"2(757)622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"555)-555-5555\") === false, 'message: <code>telephoneCheck(\"555)-555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"(555-555-5555\") === false, 'message: <code>telephoneCheck(\"(555-555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"(555)5(55?)-5555\") === false, 'message: <code>telephoneCheck(\"(555)5(55?)-5555\")</code> should return false.');"
],
"type": "bonfire",
"MDNlinks": [
"RegExp"
],
"challengeType": 5,
"isRequired": true,
"translations": {
"es": {
"title": "Valida Números Telefónicos de los EEUU",
"description": [
"Haz que la función devuelva true (verdadero) si el texto introducido parece un número válido en los EEUU.",
"El usuario debe llenar el campo del formulario de la forma que desee siempre y cuando tenga el formato de un número válido en los EEUU. Los números mostrados a continuación tienen formatos válidos en los EEUU:",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"Para esta prueba se te presentará una cadena de texto como por ejemplo: <code>800-692-7753</code> o <code>8oo-six427676;laskdjf</code>. Tu trabajo consiste en validar o rechazar el número telefónico tomando como base cualquier combinación de los formatos anteriormente presentados. El código de área es requrido. Si el código de país es provisto, debes confirmar que este es <code>1</code>. La función debe devolver true si la cadena de texto es un número telefónico válido en los EEUU; de lo contrario, debe devolver false.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"it": {
"title": "Verifica i numeri telefonici degli Stati Uniti",
"description": [
"Ritorna <code>true</code> se la stringa passata come argomento è un numero valido negli Stati Uniti.",
"L'utente può digitare qualunque stringa nel campo di inserimento, purchè sia un numero di telefono valido negli Stati Uniti. Qui sotto alcuni esempi di numeri di telefono validi negli Stati Uniti (fai riferimento ai test per le altre varianti):",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"In questo problema ti saranno presentate delle stringe come <code>800-692-7753</code> o <code>8oo-six427676;laskdjf</code>. Il tuo obiettivo è di validare o rigettare il numero di telefono basato su una qualunque combinazione dei formati specificati sopra. Il prefisso di zona è obbligatorio. Se il prefisso nazionale è presente, devi confermare che corrisponda a <code>1</code>. Ritorna <code>true</code> se la stringa è un numero di telefono valido negli Stati Uniti; altrimenti ritorna <code>false</code>.",
"Ricorda di usare <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leggi-Cerca-Chiedi</a> se rimani bloccato. Prova a programmare in coppia. Scrivi il codice da te."
]
},
"pt-br": {
"title": "Valida números telefônicos dos EUA",
"description": [
"Retorna <code>true</code> se a string passada é um número telefônico válido nos EUA.",
"O usuário pode preencher o campo de qualquer maneira com tanto que seja um número válido nos EUA. Os seguintes exemplos são formatos válidos para números de telefone nos EUA (baseie-se nos testes abaixo para outras variações):",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"Para esse desafio será dado a você uma string como <code>800-692-7753</code> ou <code>8oo-six427676;laskdjf</code>. Seu trabalho é validar ou rejeitar o número de telefone dos EUA baseado nos exmplos de formatos fornecidos acima. O código de área é obrigatório. Se o código do país for fornecido, você deve confirmar que o código do país é <code>1</code>. Retorne <code>true</code> se a string é um número válido nos EUA; caso contrário retorne <code>false</code>.",
"Lembre-se de usar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Ler-Procurar-Perguntar</a> se você ficar preso. Tente programar em par. Escreva seu próprio código."
]
}
}
},
{
"id": "aa2e6f85cab2ab736c9a9b24",
"title": "Cash Register",
"description": [
"Design a cash register drawer function <code>checkCashRegister()</code> that accepts purchase price as the first argument (<code>price</code>), payment as the second argument (<code>cash</code>), and cash-in-drawer (<code>cid</code>) as the third argument.",
"<code>cid</code> is a 2D array listing available currency.",
"The <code>checkCashRegister()</code> function should always return an object with a <code>status</code> key and a <code>change</code> key.",
"Return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code> if cash-in-drawer is less than the change due, or if you cannot return the exact change.",
"Return <code>{status: \"CLOSED\", change: [...]}</code> with cash-in-drawer as the value for the key <code>change</code> if it is equal to the change due.",
"Otherwise, return <code>{status: \"OPEN\", change: [...]}</code>, with the change due in coins and bills, sorted in highest to lowest order, as the value of the <code>change</code> key.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code.",
"<table class='table table-striped'><tr><th>Currency Unit</th><th>Amount</th></tr><tr><td>Penny</td><td>$0.01 (PENNY)</td></tr><tr><td>Nickel</td><td>$0.05 (NICKEL)</td></tr><tr><td>Dime</td><td>$0.1 (DIME)</td></tr><tr><td>Quarter</td><td>$0.25 (QUARTER)</td></tr><tr><td>Dollar</td><td>$1 (DOLLAR)</td></tr><tr><td>Five Dollars</td><td>$5 (FIVE)</td></tr><tr><td>Ten Dollars</td><td>$10 (TEN)</td></tr><tr><td>Twenty Dollars</td><td>$20 (TWENTY)</td></tr><tr><td>One-hundred Dollars</td><td>$100 (ONE HUNDRED)</td></tr></table>"
],
"challengeSeed": [
"function checkCashRegister(price, cash, cid) {",
" var change;",
" // Here is your change, ma'am.",
" return change;",
"}",
"",
"// Example cash-in-drawer array:",
"// [[\"PENNY\", 1.01],",
"// [\"NICKEL\", 2.05],",
"// [\"DIME\", 3.1],",
"// [\"QUARTER\", 4.25],",
"// [\"ONE\", 90],",
"// [\"FIVE\", 55],",
"// [\"TEN\", 20],",
"// [\"TWENTY\", 60],",
"// [\"ONE HUNDRED\", 100]]",
"",
"checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]);"
],
"solutions": [
"var denom = [\n\t{ name: 'ONE HUNDRED', val: 100},\n\t{ name: 'TWENTY', val: 20},\n\t{ name: 'TEN', val: 10},\n\t{ name: 'FIVE', val: 5},\n\t{ name: 'ONE', val: 1},\n\t{ name: 'QUARTER', val: 0.25},\n\t{ name: 'DIME', val: 0.1},\n\t{ name: 'NICKEL', val: 0.05},\n\t{ name: 'PENNY', val: 0.01}\n];\n\nfunction checkCashRegister(price, cash, cid) {\n var output = {status: null, change: []};\n var change = cash - price;\n var register = cid.reduce(function(acc, curr) {\n acc.total += curr[1];\n acc[curr[0]] = curr[1];\n return acc;\n }, {total: 0});\n if(register.total === change) {\n output.status = 'CLOSED';\n output.change = cid;\n return output;\n }\n if(register.total < change) {\n output.status = 'INSUFFICIENT_FUNDS';\n return output;\n }\n var change_arr = denom.reduce(function(acc, curr) {\n var value = 0;\n while(register[curr.name] > 0 && change >= curr.val) {\n change -= curr.val;\n register[curr.name] -= curr.val;\n value += curr.val;\n change = Math.round(change * 100) / 100;\n }\n if(value > 0) {\n acc.push([ curr.name, value ]);\n }\n return acc;\n }, []);\n if(change_arr.length < 1 || change > 0) {\n output.status = 'INSUFFICIENT_FUNDS';\n return output;\n }\n output.status = 'OPEN';\n output.change = change_arr;\n return output;\n}"
],
"tests": [
"assert.deepEqual(Object.prototype.toString.call(checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])), '[object Object]', 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return an object.');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]), {status: \"OPEN\", change: [[\"QUARTER\", 0.5]]}, 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>{status: \"OPEN\", change: [[\"QUARTER\", 0.5]]}</code>.');",
"assert.deepEqual(checkCashRegister(3.26, 100, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]), {status: \"OPEN\", change: [[\"TWENTY\", 60], [\"TEN\", 20], [\"FIVE\", 15], [\"ONE\", 1], [\"QUARTER\", 0.5], [\"DIME\", 0.2], [\"PENNY\", 0.04]]}, 'message: <code>checkCashRegister(3.26, 100, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>{status: \"OPEN\", change: [[\"TWENTY\", 60], [\"TEN\", 20], [\"FIVE\", 15], [\"ONE\", 1], [\"QUARTER\", 0.5], [\"DIME\", 0.2], [\"PENNY\", 0.04]]}</code>.');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), {status: \"INSUFFICIENT_FUNDS\", change: []}, 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code>.');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 1], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), {status: \"INSUFFICIENT_FUNDS\", change: []}, 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 1], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"INSUFFICIENT_FUNDS\", change: []}</code>.');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), {status: \"CLOSED\", change: [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]}, 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return <code>{status: \"CLOSED\", change: [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]}</code>.');"
],
"type": "bonfire",
"isRequired": true,
"MDNlinks": [
"Global Object",
"Floating Point Guide"
],
"challengeType": 5,
"translations": {
"es": {
"title": "Cambio Exacto",
"description": [
"Crea una función que simule una caja registradora que acepte el precio de compra como el primer argumento, la cantidad recibida como el segundo argumento, y la cantidad de dinero disponible en la registradora (cid) como tercer argumento",
"cid es un arreglo bidimensional que lista la cantidad de dinero disponible",
"La función debe devolver la cadena de texto \"Insufficient Funds\" si el cid es menor al cambio requerido. También debe devolver \"Closed\" si el cid es igual al cambio",
"De no ser el caso, devuelve el cambio en monedas y billetes, ordenados de mayor a menor denominación.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"it": {
"title": "Cambio Esatto",
"description": [
"Scrivi una funzione che simuli un registro di cassa chiamata <code>checkCashRegister()</code> che accetti il prezzo degli articoli come primo argomento (<code>price</code>), la somma pagata (<code>cash</code>), e la somma disponibile nel registratore di cassa (<code>cid</code>) come terzo argomento.",
"<code>cid</code> è un array a due dimensioni che contiene la quantità di monete e banconote disponibili.",
"Ritorna la stringa <code>\"Insufficient Funds\"</code> se la quantità di denaro disponibile nel registratore di cassa non è abbastanza per restituire il resto. Ritorna la stringa <code>\"Closed\"</code> se il denaro disponibile è esattamente uguale al resto.",
"Altrimenti, ritorna il resto in monete e banconote, ordinate da quelle con valore maggiore a quelle con valore minore.",
"Ricorda di usare <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leggi-Cerca-Chiedi</a> se rimani bloccato. Prova a programmare in coppia. Scrivi il codice da te."
]
},
"pt-br": {
"title": "Troco Exato",
"description": [
"Crie uma função que simula uma caixa registradora chamada <code>checkCashRegister()</code> e aceita o valor da compra como primeiro argumento (<code>price</code>), pagamento como segundo argumento (<code>cash</code>), e o dinheiro na caixa registradora (<code>cid</code>) como terceiro argumento.",
"<code>cid</code> é uma matriz bidimensional que lista o dinheiro disponível.",
"Retorne a string <code>\"Insufficient Funds\"</code> se o dinheiro na caixa registradora é menor do que o troco ou se não é possível retornar o troco exato. Retorne a string <code>\"Closed\"</code> se o dinheiro na caixa é igual ao troco.",
"Case cotrário, retorne o troco em moedas e notas, ordenado do maior para menor.",
"Lembre-se de usar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Ler-Procurar-Perguntar</a> se você ficar preso. Tente programar em par. Escreva seu próprio código.",
"<table class='table table-striped'><tr><th>Currency Unit</th><th>Amount</th></tr><tr><td>Penny</td><td>$0.01 (PENNY)</td></tr><tr><td>Nickel</td><td>$0.05 (NICKEL)</td></tr><tr><td>Dime</td><td>$0.1 (DIME)</td></tr><tr><td>Quarter</td><td>$0.25 (QUARTER)</td></tr><tr><td>Dollar</td><td>$1 (DOLLAR)</td></tr><tr><td>Five Dollars</td><td>$5 (FIVE)</td></tr><tr><td>Ten Dollars</td><td>$10 (TEN)</td></tr><tr><td>Twenty Dollars</td><td>$20 (TWENTY)</td></tr><tr><td>One-hundred Dollars</td><td>$100 (ONE HUNDRED)</td></tr></table>"
]
}
}
}
]
}

View File

@@ -8,9 +8,9 @@
"title": "Claim Your Front End Libraries Certificate", "title": "Claim Your Front End Libraries Certificate",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/vOtZumH.jpg",
"An image of our Front End Libraries Certificate", "An image of our Front End Libraries Certificate",
"This challenge will give you your verified Front End Libraries Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", "This challenge will give you your verified Front End Libraries Certificate. Before we issue your certificate, we must verify that you have completed all of our front end libraries projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -20,9 +20,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/UedoV2G.jpg", "//i.imgur.com/GJeTCMS.jpg",
"An image of the text \"Front End Development Certificate requirements\"", "An image of the text \"Front End Libraries Certificate requirements\"",
"Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. Click the button below to verify this.", "Let's confirm that you have completed all of our front end libraries projects. Click the button below to verify this.",
"#" "#"
], ],
[ [
@@ -36,11 +36,11 @@
{ {
"properties": [ "properties": [
"isHonest", "isHonest",
"isFrontEndCert" "isFrontEndLibsCert"
], ],
"apis": [ "apis": [
"/certificate/honest", "/certificate/honest",
"/certificate/verify/front-end" "/certificate/verify/front-end-libraries"
], ],
"stepIndex": [ "stepIndex": [
1, 1,
@@ -106,9 +106,9 @@
"title": "Solicite seu Certificado de Bibliotecas Front End", "title": "Solicite seu Certificado de Bibliotecas Front End",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/vOtZumH.jpg",
"Uma imagem do nosso Certificado de Bibliotecas Front End", "Uma imagem do nosso Certificado de Bibliotecas Front End",
"Este desafio lhe dará seu certificado verificado de bibliotecas Front End. Antes de emitir o seu certificado, precisamos verificar que você completou todos os nossos desafios de algoritmos básicos e intermediários e todos os nossos projetos básicos, intermediários e avançados de desenvolvimento. Você também deve aceitar nosso Compromisso de Honestidade Acadêmica. Clique no botão abaixo para iniciar este processo.", "This challenge will give you your verified Front End Libraries Certificate. Before we issue your certificate, we must verify that you have completed all of our front end libraries projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [

View File

@@ -1,5 +1,5 @@
{ {
"name": "Front End Frameworks Projects", "name": "Front End Libraries Projects",
"order": 8, "order": 8,
"time": "150 hours", "time": "150 hours",
"helpRoom": "Help", "helpRoom": "Help",

View File

@@ -50,7 +50,7 @@
}, },
"tests": [ "tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })(), 'message: The <code>DisplayMessages</code> component should render an empty <code>div</code> element.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })(), 'message: The <code>DisplayMessages</code> component should render an empty <code>div</code> element.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'message: The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.');", "getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'message: The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === 'object' && initialState.input === '' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })(), 'message: The <code>DisplayMessages</code> component should have an initial state equal to <code>{input: \"\", messages: []}</code>.');" "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === 'object' && initialState.input === '' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })(), 'message: The <code>DisplayMessages</code> component should have an initial state equal to <code>{input: \"\", messages: []}</code>.');"
], ],
"solutions": [ "solutions": [
@@ -258,7 +258,7 @@
}, },
"tests": [ "tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'message: The <code>AppWrapper</code> should render.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'message: The <code>AppWrapper</code> should render.');",
"getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/ /g,'').includes('<Providerstore={store}>'); })(), 'message: The <code>Provider</code> wrapper component should have a prop of <code>store</code> passed to it, equal to the Redux store.');", "getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\\s/g,'').includes('<Providerstore={store}>'); })(), 'message: The <code>Provider</code> wrapper component should have a prop of <code>store</code> passed to it, equal to the Redux store.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })(), 'message: <code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })(), 'message: <code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('div').length === 1 && mockedComponent.find('h2').length === 1 && mockedComponent.find('button').length === 1 && mockedComponent.find('ul').length === 1; })(), 'message: The <code>DisplayMessages</code> component should render an h2, input, button, and <code>ul</code> element.');" "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('div').length === 1 && mockedComponent.find('h2').length === 1 && mockedComponent.find('button').length === 1 && mockedComponent.find('ul').length === 1; })(), 'message: The <code>DisplayMessages</code> component should render an h2, input, button, and <code>ul</code> element.');"
], ],

View File

@@ -12,7 +12,7 @@
"src": "https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js" "src": "https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"
} }
], ],
"template": "<body><div id='root'></div>${ source }</body>", "template": "<body><div id='root'></div><div id='challenge-node'></div>${ source }</body>",
"challenges": [ "challenges": [
{ {
"id": "587d7dbc367417b2b2512bb1", "id": "587d7dbc367417b2b2512bb1",
@@ -143,11 +143,7 @@
"releasedOn": "December 25, 2017", "releasedOn": "December 25, 2017",
"description": [ "description": [
"So far, you've learned that JSX is a convenient tool to write readable HTML within JavaScript. With React, we can render this JSX directly to the HTML DOM using React's rendering API known as ReactDOM.", "So far, you've learned that JSX is a convenient tool to write readable HTML within JavaScript. With React, we can render this JSX directly to the HTML DOM using React's rendering API known as ReactDOM.",
"ReactDOM offers a simple method to render React elements to the DOM which looks like this: <code>ReactDOM.render(componentToRender, targetNode)</code>.", "ReactDOM offers a simple method to render React elements to the DOM which looks like this: <code>ReactDOM.render(componentToRender, targetNode)</code>, where the first argument is the React element or component that you want to render, and the second argument is the DOM node that you want to render the component to.",
"<ul>",
"<li>The first argument is the React element or component that you want to render.</li>",
"<li>The second argument is the DOM node that you want to render the component within.</li>",
"</ul>",
"As you would expect, <code>ReactDOM.render()</code> must be called after the JSX element declarations, just like how you must declare variables before using them.", "As you would expect, <code>ReactDOM.render()</code> must be called after the JSX element declarations, just like how you must declare variables before using them.",
"<hr>", "<hr>",
"The code editor has a simple JSX component. Use the <code>ReactDOM.render()</code> method to render this component to the page. You can pass defined JSX elements directly in as the first argument and use <code>document.getElementById()</code> to select the DOM node to render them to. There is a <code>div</code> with <code>id='challenge-node'</code> available for you to use. Make sure you don't change the <code>JSX</code> constant." "The code editor has a simple JSX component. Use the <code>ReactDOM.render()</code> method to render this component to the page. You can pass defined JSX elements directly in as the first argument and use <code>document.getElementById()</code> to select the DOM node to render them to. There is a <code>div</code> with <code>id='challenge-node'</code> available for you to use. Make sure you don't change the <code>JSX</code> constant."
@@ -166,8 +162,7 @@
");", ");",
"// change code below this line", "// change code below this line",
"" ""
], ]
"tail": "ReactDOM.render(JSX, document.getElementById('root'))"
} }
}, },
"tests": [ "tests": [
@@ -251,9 +246,9 @@
" remove comment and change code above this line */}", " remove comment and change code above this line */}",
" </div>", " </div>",
");", ");",
"", ""
"ReactDOM.render(JSX, document.getElementById('root'));" ],
] "tail": "ReactDOM.render(JSX, document.getElementById('root'))"
} }
}, },
"tests": [ "tests": [
@@ -680,8 +675,7 @@
"", "",
"// change code below this line", "// change code below this line",
"" ""
], ]
"tail": "ReactDOM.render(<TypesOfFood />, document.getElementById('root'))"
} }
}, },
"tests": [ "tests": [
@@ -716,8 +710,7 @@
"contents": [ "contents": [
"// change code below this line", "// change code below this line",
"" ""
], ]
"tail": "ReactDOM.render(<MyComponent />, document.getElementById('root'))"
} }
}, },
"tests": [ "tests": [
@@ -1129,8 +1122,8 @@
"tests": [ "tests": [
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('CampSite').length === 1; })(), 'message: The <code>CampSite</code> component should render.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('CampSite').length === 1; })(), 'message: The <code>CampSite</code> component should render.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('Camper').length === 1; })(), 'message: The <code>Camper</code> component should render.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('Camper').length === 1; })(), 'message: The <code>Camper</code> component should render.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g, '').replace(/\\r?\\n|\\r/g, ''); const verify1 = 'Camper.defaultProps={name:\\'CamperBot\\'}'; const verify2 = 'Camper.defaultProps={name:\"CamperBot\"}'; return (noWhiteSpace.includes(verify1) || noWhiteSpace.includes(verify2)); })(), 'message: The <code>Camper</code> component should include default props which assign the string <code>CamperBot</code> to the key <code>name</code>.');", "getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\\s/g, ''); const verify1 = 'Camper.defaultProps={name:\\'CamperBot\\'}'; const verify2 = 'Camper.defaultProps={name:\"CamperBot\"}'; return (noWhiteSpace.includes(verify1) || noWhiteSpace.includes(verify2)); })(), 'message: The <code>Camper</code> component should include default props which assign the string <code>CamperBot</code> to the key <code>name</code>.');",
"getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); const noWhiteSpace = getUserInput('index').replace(/ /g, '').replace(/\\r?\\n|\\r/g, ''); const verifyDefaultProps = 'Camper.propTypes={name:PropTypes.string.isRequired'; return noWhiteSpace.includes(verifyDefaultProps); })(), 'message: The <code>Camper</code> component should include prop types which require the <code>name</code> prop to be of type <code>string</code>.');", "getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); const noWhiteSpace = getUserInput('index').replace(/\\s/g, ''); const verifyDefaultProps = 'Camper.propTypes={name:PropTypes.string.isRequired}'; return noWhiteSpace.includes(verifyDefaultProps); })(), 'message: The <code>Camper</code> component should include prop types which require the <code>name</code> prop to be of type <code>string</code>.');",
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('p').text() === mockedComponent.find('Camper').props().name; })(), 'message: The <code>Camper</code> component should contain a <code>p</code> element with only the text from the <code>name</code> prop.');" "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('p').text() === mockedComponent.find('Camper').props().name; })(), 'message: The <code>Camper</code> component should contain a <code>p</code> element with only the text from the <code>name</code> prop.');"
], ],
"solutions": [ "solutions": [
@@ -1235,7 +1228,7 @@
"tests": [ "tests": [
"assert(Enzyme.mount(React.createElement(MyComponent)).state('name') === 'freeCodeCamp', 'message: <code>MyComponent</code> should have a key <code>name</code> with value <code>freeCodeCamp</code> stored in its state.');", "assert(Enzyme.mount(React.createElement(MyComponent)).state('name') === 'freeCodeCamp', 'message: <code>MyComponent</code> should have a key <code>name</code> with value <code>freeCodeCamp</code> stored in its state.');",
"assert(/<div><h1>.*<\\/h1><\\/div>/.test(Enzyme.mount(React.createElement(MyComponent)).html()), 'message: <code>MyComponent</code> should render an <code>h1</code> header enclosed in a single <code>div</code>.');", "assert(/<div><h1>.*<\\/h1><\\/div>/.test(Enzyme.mount(React.createElement(MyComponent)).html()), 'message: <code>MyComponent</code> should render an <code>h1</code> header enclosed in a single <code>div</code>.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'TestName' }); return waitForIt(() => mockedComponent.html()) }; const firstValue = await first(); assert(firstValue === '<div><h1>TestName</h1></div>', 'message: The rendered <code>h1</code> header should contain text rendered from the component's state.'); };" "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'TestName' }); return waitForIt(() => mockedComponent.html()) }; const firstValue = await first(); assert(firstValue === '<div><h1>TestName</h1></div>', 'message: The rendered <code>h1</code> header should contain text rendered from the component&apos;s state.');};"
], ],
"solutions": [ "solutions": [
"class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n name: 'freeCodeCamp'\n }\n }\n render() {\n return (\n <div>\n { /* change code below this line */ }\n <h1>{this.state.name}</h1>\n { /* change code above this line */ }\n </div>\n );\n }\n};" "class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n name: 'freeCodeCamp'\n }\n }\n render() {\n return (\n <div>\n { /* change code below this line */ }\n <h1>{this.state.name}</h1>\n { /* change code above this line */ }\n </div>\n );\n }\n};"
@@ -1289,7 +1282,7 @@
"assert(Enzyme.mount(React.createElement(MyComponent)).state('name') === 'freeCodeCamp', 'message: <code>MyComponent</code> should have a key <code>name</code> with value <code>freeCodeCamp</code> stored in its state.');", "assert(Enzyme.mount(React.createElement(MyComponent)).state('name') === 'freeCodeCamp', 'message: <code>MyComponent</code> should have a key <code>name</code> with value <code>freeCodeCamp</code> stored in its state.');",
"assert(/<div><h1>.*<\\/h1><\\/div>/.test(Enzyme.mount(React.createElement(MyComponent)).html()), 'message: <code>MyComponent</code> should render an <code>h1</code> header enclosed in a single <code>div</code>.');", "assert(/<div><h1>.*<\\/h1><\\/div>/.test(Enzyme.mount(React.createElement(MyComponent)).html()), 'message: <code>MyComponent</code> should render an <code>h1</code> header enclosed in a single <code>div</code>.');",
"getUserInput => assert(/<h1>\\n*\\s*\\{\\s*name\\s*\\}\\s*\\n*<\\/h1>/.test(getUserInput('index')), 'message: The rendered <code>h1</code> tag should include a reference to <code>{name}</code>.');", "getUserInput => assert(/<h1>\\n*\\s*\\{\\s*name\\s*\\}\\s*\\n*<\\/h1>/.test(getUserInput('index')), 'message: The rendered <code>h1</code> tag should include a reference to <code>{name}</code>.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'TestName' }); return waitForIt(() => mockedComponent.html()) }; const firstValue = await first(); assert(firstValue === '<div><h1>TestName</h1></div>', 'message: The rendered <code>h1</code> header should contain text rendered from the component's state.'); };" "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'TestName' }); return waitForIt(() => mockedComponent.html()) }; const firstValue = await first(); assert(firstValue === '<div><h1>TestName</h1></div>', 'message: The rendered <code>h1</code> header should contain text rendered from the component&apos;s state.'); };"
], ],
"solutions": [ "solutions": [
"class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n name: 'freeCodeCamp'\n }\n }\n render() {\n // change code below this line\n const name = this.state.name;\n // change code above this line\n return (\n <div>\n { /* change code below this line */ }\n <h1>{name}</h1>\n { /* change code above this line */ }\n </div>\n );\n }\n};" "class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n name: 'freeCodeCamp'\n }\n }\n render() {\n // change code below this line\n const name = this.state.name;\n // change code above this line\n return (\n <div>\n { /* change code below this line */ }\n <h1>{name}</h1>\n { /* change code above this line */ }\n </div>\n );\n }\n};"
@@ -1346,7 +1339,7 @@
"tests": [ "tests": [
"assert(Enzyme.mount(React.createElement(MyComponent)).state('name') === 'Initial State', 'message: The state of <code>MyComponent</code> should initialize with the key value pair <code>{ name: Initial State }</code>.');", "assert(Enzyme.mount(React.createElement(MyComponent)).state('name') === 'Initial State', 'message: The state of <code>MyComponent</code> should initialize with the key value pair <code>{ name: Initial State }</code>.');",
"assert(Enzyme.mount(React.createElement(MyComponent)).find('h1').length === 1, 'message: <code>MyComponent</code> should render an <code>h1</code> header.');", "assert(Enzyme.mount(React.createElement(MyComponent)).find('h1').length === 1, 'message: <code>MyComponent</code> should render an <code>h1</code> header.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'TestName' }); return waitForIt(() => mockedComponent.html()); }; const firstValue = await first(); assert(/<h1>TestName<\\/h1>/.test(firstValue), 'message: The rendered <code>h1</code> header should contain text rendered from the component's state.'); };", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'TestName' }); return waitForIt(() => mockedComponent.html()); }; const firstValue = await first(); assert(/<h1>TestName<\\/h1>/.test(firstValue), 'message: The rendered <code>h1</code> header should contain text rendered from the component&apos;s state.'); };",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'Before' }); return waitForIt(() => mockedComponent.state('name')); }; const second = () => { mockedComponent.setState({ name: 'React Rocks!' }); return waitForIt(() => mockedComponent.state('name')); }; const firstValue = await first(); const secondValue = await second(); assert(firstValue === 'Before' && secondValue === 'React Rocks!', 'message: Calling the <code>handleClick</code> method on <code>MyComponent</code> should set the name property in state to equal <code>React Rocks!</code>.'); }; " "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ name: 'Before' }); return waitForIt(() => mockedComponent.state('name')); }; const second = () => { mockedComponent.setState({ name: 'React Rocks!' }); return waitForIt(() => mockedComponent.state('name')); }; const firstValue = await first(); const secondValue = await second(); assert(firstValue === 'Before' && secondValue === 'React Rocks!', 'message: Calling the <code>handleClick</code> method on <code>MyComponent</code> should set the name property in state to equal <code>React Rocks!</code>.'); }; "
], ],
"solutions": [ "solutions": [
@@ -1658,9 +1651,9 @@
"tests": [ "tests": [
"assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyForm)); return (mockedComponent.find('div').children().find('form').length === 1 && mockedComponent.find('div').children().find('h1').length === 1 && mockedComponent.find('form').children().find('input').length === 1 && mockedComponent.find('form').children().find('button').length === 1) })(), 'message: <code>MyForm</code> should return a <code>div</code> element which contains a <code>form</code> and an <code>h1</code> tag. The form should include an <code>input</code> and a <code>button</code>.');", "assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyForm)); return (mockedComponent.find('div').children().find('form').length === 1 && mockedComponent.find('div').children().find('h1').length === 1 && mockedComponent.find('form').children().find('input').length === 1 && mockedComponent.find('form').children().find('button').length === 1) })(), 'message: <code>MyForm</code> should return a <code>div</code> element which contains a <code>form</code> and an <code>h1</code> tag. The form should include an <code>input</code> and a <code>button</code>.');",
"assert(Enzyme.mount(React.createElement(MyForm)).state('input') === '' && Enzyme.mount(React.createElement(MyForm)).state('submit') === '', 'message: The state of <code>MyForm</code> should initialize with <code>input</code> and <code>submit</code> properties, both set to empty strings.');", "assert(Enzyme.mount(React.createElement(MyForm)).state('input') === '' && Enzyme.mount(React.createElement(MyForm)).state('submit') === '', 'message: The state of <code>MyForm</code> should initialize with <code>input</code> and <code>submit</code> properties, both set to empty strings.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '' }); return waitForIt(() => mockedComponent.state('input'))}; const _2 = () => { mockedComponent.find('input').simulate('change', { target: { value: 'TestInput' }}); return waitForIt(() => ({ state: mockedComponent.state('input'), inputVal: mockedComponent.find('input').props().value }))}; const before = await _1(); const after = await _2(); assert(before === '' && after.state === 'TestInput' && after.inputVal === 'TestInput', 'message: Typing in the <code>input</code> element should update the <code>input</code> property of the component's state.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '' }); return waitForIt(() => mockedComponent.state('input'))}; const _2 = () => { mockedComponent.find('input').simulate('change', { target: { value: 'TestInput' }}); return waitForIt(() => ({ state: mockedComponent.state('input'), inputVal: mockedComponent.find('input').props().value }))}; const before = await _1(); const after = await _2(); assert(before === '' && after.state === 'TestInput' && after.inputVal === 'TestInput', 'message: Typing in the <code>input</code> element should update the <code>input</code> property of the component&apos;s state.'); }; ",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '' }); mockedComponent.setState({submit: ''}); mockedComponent.find('input').simulate('change', {target: {value: 'SubmitInput'}}); return waitForIt(() => mockedComponent.state('submit'))}; const _2 = () => { mockedComponent.find('form').simulate('submit'); return waitForIt(() => mockedComponent.state('submit'))}; const before = await _1(); const after = await _2(); assert(before === '' && after === 'SubmitInput', 'message: Submitting the form should run <code>handleSubmit</code> which should set the <code>submit</code> property in state equal to the current input.'); };", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '' }); mockedComponent.setState({submit: ''}); mockedComponent.find('input').simulate('change', {target: {value: 'SubmitInput'}}); return waitForIt(() => mockedComponent.state('submit'))}; const _2 = () => { mockedComponent.find('form').simulate('submit'); return waitForIt(() => mockedComponent.state('submit'))}; const before = await _1(); const after = await _2(); assert(before === '' && after === 'SubmitInput', 'message: Submitting the form should run <code>handleSubmit</code> which should set the <code>submit</code> property in state equal to the current input.'); };",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '' }); mockedComponent.setState({submit: ''}); mockedComponent.find('input').simulate('change', {target: {value: 'TestInput'}}); return waitForIt(() => mockedComponent.find('h1').text())}; const _2 = () => { mockedComponent.find('form').simulate('submit'); return waitForIt(() => mockedComponent.find('h1').text())}; const before = await _1(); const after = await _2(); assert(before === '' && after === 'TestInput', 'message: The <code>h1</code> header should render the value of the <code>submit</code> field from the component's state.'); }; " "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyForm)); const _1 = () => { mockedComponent.setState({ input: '' }); mockedComponent.setState({submit: ''}); mockedComponent.find('input').simulate('change', {target: {value: 'TestInput'}}); return waitForIt(() => mockedComponent.find('h1').text())}; const _2 = () => { mockedComponent.find('form').simulate('submit'); return waitForIt(() => mockedComponent.find('h1').text())}; const before = await _1(); const after = await _2(); assert(before === '' && after === 'TestInput', 'message: The <code>h1</code> header should render the value of the <code>submit</code> field from the component&apos;s state.'); }; "
], ],
"solutions": [ "solutions": [
"class MyForm extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n submit: ''\n };\n this.handleChange = this.handleChange.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n handleSubmit(event) {\n event.preventDefault()\n this.setState({\n submit: this.state.input\n });\n }\n render() {\n return (\n <div>\n <form onSubmit={this.handleSubmit}>\n <input\n value={this.state.input}\n onChange={this.handleChange} />\n <button type='submit'>Submit!</button>\n </form>\n <h1>{this.state.submit}</h1>\n </div>\n );\n }\n};" "class MyForm extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n submit: ''\n };\n this.handleChange = this.handleChange.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n handleSubmit(event) {\n event.preventDefault()\n this.setState({\n submit: this.state.input\n });\n }\n render() {\n return (\n <div>\n <form onSubmit={this.handleSubmit}>\n <input\n value={this.state.input}\n onChange={this.handleChange} />\n <button type='submit'>Submit!</button>\n </form>\n <h1>{this.state.submit}</h1>\n </div>\n );\n }\n};"
@@ -1739,7 +1732,7 @@
"description": [ "description": [
"You can pass <code>state</code> as props to child components, but you're not limited to passing data. You can also pass handler functions or any method that's defined on a React component to a child component. This is how you allow child components to interact with their parent components. You pass methods to a child just like a regular prop. It's assigned a name and you have access to that method name under <code>this.props</code> in the child component.", "You can pass <code>state</code> as props to child components, but you're not limited to passing data. You can also pass handler functions or any method that's defined on a React component to a child component. This is how you allow child components to interact with their parent components. You pass methods to a child just like a regular prop. It's assigned a name and you have access to that method name under <code>this.props</code> in the child component.",
"<hr>", "<hr>",
"There are three components outlined in the code editor. The <code>MyApp</code> component is the parent that will render the <code>GetInput</code> and <code>RenderInput</code> child components. Add the <code>GetInput</code> component to the render method in <code>MyApp</code>, then pass it a prop called <code>input</code> assigned to <code>inputValue</code> from <code>MyApp</code>'s <code>state</code>. Also create a prop called <code>handleChange</code> and pass the input handler <code>handleChange</code> to it.", "There are three components outlined in the code editor. The <code>MyApp</code> component is the parent that will render the <code>GetInput</code> and <code>RenderInput</code> child components. Add the <code>GetInput</code> component to the render method in <code>MyApp</code>, then pass it a prop called <code>input</code> assigned to <code>inputValue</code> from <code>MyApp</code>&apos;s <code>state</code>. Also create a prop called <code>handleChange</code> and pass the input handler <code>handleChange</code> to it.",
"Next, add <code>RenderInput</code> to the render method in <code>MyApp</code>, then create a prop called <code>input</code> and pass the <code>inputValue</code> from <code>state</code> to it. Once you are finished you will be able to type in the <code>input</code> field in the <code>GetInput</code> component, which then calls the handler method in its parent via props. This updates the input in the <code>state</code> of the parent, which is passed as props to both children. Observe how the data flows between the components and how the single source of truth remains the <code>state</code> of the parent component. Admittedly, this example is a bit contrived, but should serve to illustrate how data and callbacks can be passed between React components." "Next, add <code>RenderInput</code> to the render method in <code>MyApp</code>, then create a prop called <code>input</code> and pass the <code>inputValue</code> from <code>state</code> to it. Once you are finished you will be able to type in the <code>input</code> field in the <code>GetInput</code> component, which then calls the handler method in its parent via props. This updates the input in the <code>state</code> of the parent, which is passed as props to both children. Observe how the data flows between the components and how the single source of truth remains the <code>state</code> of the parent component. Admittedly, this example is a bit contrived, but should serve to illustrate how data and callbacks can be passed between React components."
], ],
"files": { "files": {
@@ -1917,7 +1910,7 @@
"tests": [ "tests": [
"assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return (mockedComponent.find('div').length === 1 && mockedComponent.find('h1').length === 1); })(), 'message: <code>MyComponent</code> should render a <code>div</code> element which wraps an <code>h1</code> tag.');", "assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return (mockedComponent.find('div').length === 1 && mockedComponent.find('h1').length === 1); })(), 'message: <code>MyComponent</code> should render a <code>div</code> element which wraps an <code>h1</code> tag.');",
"assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return new RegExp('setTimeout(.|\\n)+setState(.|\\n)+activeUsers').test(String(mockedComponent.instance().componentDidMount)); })(), 'message: Component state should be updated with a timeout function in <code>componentDidMount</code>.');", "assert((() => { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return new RegExp('setTimeout(.|\\n)+setState(.|\\n)+activeUsers').test(String(mockedComponent.instance().componentDidMount)); })(), 'message: Component state should be updated with a timeout function in <code>componentDidMount</code>.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ activeUsers: 1237 }); return waitForIt(() => mockedComponent.find('h1').text()); }; const second = () => { mockedComponent.setState({ activeUsers: 1000 }); return waitForIt(() => mockedComponent.find('h1').text()); }; const firstValue = await first(); const secondValue = await second(); assert(new RegExp('1237').test(firstValue) && new RegExp('1000').test(secondValue), 'message: The <code>h1</code> tag should render the <code>activeUsers</code> value from <code>MyComponent</code>'s state.); }; " "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const first = () => { mockedComponent.setState({ activeUsers: 1237 }); return waitForIt(() => mockedComponent.find('h1').text()); }; const second = () => { mockedComponent.setState({ activeUsers: 1000 }); return waitForIt(() => mockedComponent.find('h1').text()); }; const firstValue = await first(); const secondValue = await second(); assert(new RegExp('1237').test(firstValue) && new RegExp('1000').test(secondValue), 'message: The <code>h1</code> tag should render the <code>activeUsers</code> value from <code>MyComponent</code>&apos;s state.'); }; "
], ],
"solutions": [ "solutions": [
"class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n activeUsers: null\n };\n }\n componentDidMount() {\n setTimeout( () => {\n this.setState({\n activeUsers: 1273\n });\n }, 2500);\n }\n render() {\n return (\n <div>\n <h1>Active Users: {this.state.activeUsers}</h1>\n </div>\n );\n }\n};" "class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n activeUsers: null\n };\n }\n componentDidMount() {\n setTimeout( () => {\n this.setState({\n activeUsers: 1273\n });\n }, 2500);\n }\n render() {\n return (\n <div>\n <h1>Active Users: {this.state.activeUsers}</h1>\n </div>\n );\n }\n};"
@@ -2340,9 +2333,9 @@
}, },
"tests": [ "tests": [
"assert.strictEqual(Enzyme.mount(React.createElement(MagicEightBall)).find('MagicEightBall').length, 1, 'message: The <code>MagicEightBall</code> compponent should exist and should render to the page.');", "assert.strictEqual(Enzyme.mount(React.createElement(MagicEightBall)).find('MagicEightBall').length, 1, 'message: The <code>MagicEightBall</code> compponent should exist and should render to the page.');",
"assert.strictEqual(Enzyme.mount(React.createElement(MagicEightBall)).children().childAt(0).name(), 'input', \"message: <code>MagicEightBall</code>'s first child should be an <code>input</code> element.\");", "assert.strictEqual(Enzyme.mount(React.createElement(MagicEightBall)).children().childAt(0).name(), 'input', \"message: <code>MagicEightBall</code>&apos;s first child should be an <code>input</code> element.\");",
"assert.strictEqual(Enzyme.mount(React.createElement(MagicEightBall)).children().childAt(2).name(), 'button', \"message: <code>MagicEightBall</code>'s third child should be a <code>button</code> element.\");", "assert.strictEqual(Enzyme.mount(React.createElement(MagicEightBall)).children().childAt(2).name(), 'button', \"message: <code>MagicEightBall</code>&apos;s third child should be a <code>button</code> element.\");",
"assert(Enzyme.mount(React.createElement(MagicEightBall)).state('randomIndex') === '' && Enzyme.mount(React.createElement(MagicEightBall)).state('userInput') === '', \"message: <code>MagicEightBall</code>'s state should be initialized with a property of <code>userInput</code> and a property of <code>randomIndex</code> both set to a value of an empty string.\");", "assert(Enzyme.mount(React.createElement(MagicEightBall)).state('randomIndex') === '' && Enzyme.mount(React.createElement(MagicEightBall)).state('userInput') === '', \"message: <code>MagicEightBall</code>&apos;s state should be initialized with a property of <code>userInput</code> and a property of <code>randomIndex</code> both set to a value of an empty string.\");",
"assert(Enzyme.mount(React.createElement(MagicEightBall)).find('p').length === 1 && Enzyme.mount(React.createElement(MagicEightBall)).find('p').text() === '', 'message: When <code>MagicEightBall</code> is first mounted to the DOM, it should return an empty <code>p</code> element.');", "assert(Enzyme.mount(React.createElement(MagicEightBall)).find('p').length === 1 && Enzyme.mount(React.createElement(MagicEightBall)).find('p').text() === '', 'message: When <code>MagicEightBall</code> is first mounted to the DOM, it should return an empty <code>p</code> element.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MagicEightBall)); const simulate = () => { comp.find('input').simulate('change', { target: { value: 'test?' }}); comp.find('button').simulate('click'); }; const result = () => comp.find('p').text(); const _1 = () => { simulate(); return waitForIt(() => result()) }; const _2 = () => { simulate(); return waitForIt(() => result()) }; const _3 = () => { simulate(); return waitForIt(() => result()) }; const _4 = () => { simulate(); return waitForIt(() => result()) }; const _5 = () => { simulate(); return waitForIt(() => result()) }; const _6 = () => { simulate(); return waitForIt(() => result()) }; const _7 = () => { simulate(); return waitForIt(() => result()) }; const _8 = () => { simulate(); return waitForIt(() => result()) }; const _9 = () => { simulate(); return waitForIt(() => result()) }; const _10 = () => { simulate(); return waitForIt(() => result()) }; const _1_val = await _1(); const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); const _5_val = await _5(); const _6_val = await _6(); const _7_val = await _7(); const _8_val = await _8(); const _9_val = await _9(); const _10_val = await _10(); const actualAnswers = [_1_val, _2_val, _3_val, _4_val, _5_val, _6_val, _7_val, _8_val, _9_val, _10_val]; const hasIndex = actualAnswers.filter((answer, i) => possibleAnswers.indexOf(answer) !== -1); const notAllEqual = new Set(actualAnswers); assert(notAllEqual.size > 1 && hasIndex.length === 10, 'message: When text is entered into the <code>input</code> element and the button is clicked, the <code>MagicEightBall</code> compponent should return a <code>p</code> element that contains a random element from the <code>possibleAnswers</code> array.'); }; " "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MagicEightBall)); const simulate = () => { comp.find('input').simulate('change', { target: { value: 'test?' }}); comp.find('button').simulate('click'); }; const result = () => comp.find('p').text(); const _1 = () => { simulate(); return waitForIt(() => result()) }; const _2 = () => { simulate(); return waitForIt(() => result()) }; const _3 = () => { simulate(); return waitForIt(() => result()) }; const _4 = () => { simulate(); return waitForIt(() => result()) }; const _5 = () => { simulate(); return waitForIt(() => result()) }; const _6 = () => { simulate(); return waitForIt(() => result()) }; const _7 = () => { simulate(); return waitForIt(() => result()) }; const _8 = () => { simulate(); return waitForIt(() => result()) }; const _9 = () => { simulate(); return waitForIt(() => result()) }; const _10 = () => { simulate(); return waitForIt(() => result()) }; const _1_val = await _1(); const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); const _5_val = await _5(); const _6_val = await _6(); const _7_val = await _7(); const _8_val = await _8(); const _9_val = await _9(); const _10_val = await _10(); const actualAnswers = [_1_val, _2_val, _3_val, _4_val, _5_val, _6_val, _7_val, _8_val, _9_val, _10_val]; const hasIndex = actualAnswers.filter((answer, i) => possibleAnswers.indexOf(answer) !== -1); const notAllEqual = new Set(actualAnswers); assert(notAllEqual.size > 1 && hasIndex.length === 10, 'message: When text is entered into the <code>input</code> element and the button is clicked, the <code>MagicEightBall</code> compponent should return a <code>p</code> element that contains a random element from the <code>possibleAnswers</code> array.'); }; "
], ],
@@ -2402,7 +2395,7 @@
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('MyComponent').length === 1; })(), 'message: <code>MyComponent</code> should exist and render.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('MyComponent').length === 1; })(), 'message: <code>MyComponent</code> should exist and render.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: true}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(mockedComponent.find('div').length === 1 && mockedComponent.find('div').children().length === 2 && mockedComponent.find('button').length === 1 && mockedComponent.find('h1').length === 1, 'message: When <code>display</code> is set to <code>true</code>, a <code>div</code>, <code>button</code>, and <code>h1</code> should render.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: true}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(mockedComponent.find('div').length === 1 && mockedComponent.find('div').children().length === 2 && mockedComponent.find('button').length === 1 && mockedComponent.find('h1').length === 1, 'message: When <code>display</code> is set to <code>true</code>, a <code>div</code>, <code>button</code>, and <code>h1</code> should render.'); }; ",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: false}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(mockedComponent.find('div').length === 1 && mockedComponent.find('div').children().length === 1 && mockedComponent.find('button').length === 1 && mockedComponent.find('h1').length === 0, 'message: When <code>display</code> is set to <code>false</code>, only a <code>div</code> and <code>button</code> should render.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: false}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(mockedComponent.find('div').length === 1 && mockedComponent.find('div').children().length === 1 && mockedComponent.find('button').length === 1 && mockedComponent.find('h1').length === 0, 'message: When <code>display</code> is set to <code>false</code>, only a <code>div</code> and <code>button</code> should render.'); }; ",
"getUserInput => assert(getUserInput('index').includes('if') === true && getUserInput('index').includes('else') === true, 'message: The render method should use an <code>if/else</code> statement to check the condition of <code>this.state.display</code>.');" "getUserInput => assert(getUserInput('index').includes('if') && getUserInput('index').includes('else'), 'message: The render method should use an <code>if/else</code> statement to check the condition of <code>this.state.display</code>.');"
], ],
"solutions": [ "solutions": [
"class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n display: true\n }\n this.toggleDisplay = this.toggleDisplay.bind(this); \n }\n toggleDisplay() {\n this.setState({\n display: !this.state.display\n });\n }\n render() {\n // change code below this line\n if (this.state.display) {\n return (\n <div>\n <button onClick={this.toggleDisplay}>Toggle Display</button>\n <h1>Displayed!</h1>\n </div>\n );\n } else {\n return (\n <div>\n <button onClick={this.toggleDisplay}>Toggle Display</button>\n </div>\n );\n }\n }\n};" "class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n display: true\n }\n this.toggleDisplay = this.toggleDisplay.bind(this); \n }\n toggleDisplay() {\n this.setState({\n display: !this.state.display\n });\n }\n render() {\n // change code below this line\n if (this.state.display) {\n return (\n <div>\n <button onClick={this.toggleDisplay}>Toggle Display</button>\n <h1>Displayed!</h1>\n </div>\n );\n } else {\n return (\n <div>\n <button onClick={this.toggleDisplay}>Toggle Display</button>\n </div>\n );\n }\n }\n};"
@@ -2460,7 +2453,7 @@
"assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('MyComponent').length; })(), 'message: <code>MyComponent</code> should exist and render.');", "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('MyComponent').length; })(), 'message: <code>MyComponent</code> should exist and render.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: true}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(updated.find('div').length === 1 && updated.find('div').children().length === 2 && updated.find('button').length === 1 && updated.find('h1').length === 1, 'message: When <code>display</code> is set to <code>true</code>, a <code>div</code>, <code>button</code>, and <code>h1</code> should render.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: true}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(updated.find('div').length === 1 && updated.find('div').children().length === 2 && updated.find('button').length === 1 && updated.find('h1').length === 1, 'message: When <code>display</code> is set to <code>true</code>, a <code>div</code>, <code>button</code>, and <code>h1</code> should render.'); }; ",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: false}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(updated.find('div').length === 1 && updated.find('div').children().length === 1 && updated.find('button').length === 1 && updated.find('h1').length === 0, 'message: When <code>display</code> is set to <code>false</code>, only a <code>div</code> and <code>button</code> should render.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); const state_1 = () => { mockedComponent.setState({display: false}); return waitForIt(() => mockedComponent )}; const updated = await state_1(); assert(updated.find('div').length === 1 && updated.find('div').children().length === 1 && updated.find('button').length === 1 && updated.find('h1').length === 0, 'message: When <code>display</code> is set to <code>false</code>, only a <code>div</code> and <code>button</code> should render.'); }; ",
"getUserInput=> assert(getUserInput('index').includes('&&'), 'message: The render method should use the && logical operator to check the condition of this.state.display.');" "getUserInput => assert(getUserInput('index').includes('&&'), 'message: The render method should use the && logical operator to check the condition of this.state.display.');"
], ],
"solutions": [ "solutions": [
"class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n display: true\n }\n this.toggleDisplay = this.toggleDisplay.bind(this); \n }\n toggleDisplay() {\n this.setState({\n display: !this.state.display\n });\n }\n render() {\n // change code below this line\n return (\n <div>\n <button onClick={this.toggleDisplay}>Toggle Display</button>\n {this.state.display && <h1>Displayed!</h1>}\n </div>\n );\n }\n};" "class MyComponent extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n display: true\n }\n this.toggleDisplay = this.toggleDisplay.bind(this); \n }\n toggleDisplay() {\n this.setState({\n display: !this.state.display\n });\n }\n render() {\n // change code below this line\n return (\n <div>\n <button onClick={this.toggleDisplay}>Toggle Display</button>\n {this.state.display && <h1>Displayed!</h1>}\n </div>\n );\n }\n};"
@@ -2538,10 +2531,10 @@
}, },
"tests": [ "tests": [
"assert(Enzyme.mount(React.createElement(CheckUserAge)).find('div').find('input').length === 1 && Enzyme.mount(React.createElement(CheckUserAge)).find('div').find('button').length === 1, 'message: The <code>CheckUserAge</code> component should render with a single <code>input</code> element and a single <code>button</code> element.');", "assert(Enzyme.mount(React.createElement(CheckUserAge)).find('div').find('input').length === 1 && Enzyme.mount(React.createElement(CheckUserAge)).find('div').find('button').length === 1, 'message: The <code>CheckUserAge</code> component should render with a single <code>input</code> element and a single <code>button</code> element.');",
"assert(Enzyme.mount(React.createElement(CheckUserAge)).state().input === '' && Enzyme.mount(React.createElement(CheckUserAge)).state().userAge === '', \"message: The <code>CheckUserAge</code> component's state should be initialized with a property of <code>userAge</code> and a property of <code>input</code>, both set to a value of an empty string.\");", "assert(Enzyme.mount(React.createElement(CheckUserAge)).state().input === '' && Enzyme.mount(React.createElement(CheckUserAge)).state().userAge === '', 'message: The <code>CheckUserAge</code> component&apos;s state should be initialized with a property of <code>userAge</code> and a property of <code>input</code>, both set to a value of an empty string.');",
"assert(Enzyme.mount(React.createElement(CheckUserAge)).find('button').text() === 'Submit', \"message: When the <code>CheckUserAge</code> component is first rendered to the DOM, the <code>button</code>'s inner text should be Submit.\");", "assert(Enzyme.mount(React.createElement(CheckUserAge)).find('button').text() === 'Submit', 'message: When the <code>CheckUserAge</code> component is first rendered to the DOM, the <code>button</code>&apos;s inner text should be Submit.');",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const initialButton = mockedComponent.find('button').text(); const enter3AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 3 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const enter17AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 17 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const userAge3 = await enter3AndClickButton(); const userAge17 = await enter17AndClickButton(); assert(initialButton === 'Submit' && userAge3 === 'You Shall Not Pass' && userAge17 === 'You Shall Not Pass', 'message: When a number of less than 18 is entered into the <code>input</code> element and the <code>button</code> is clicked, the <code>button</code>'s inner text should read <code>You Shall Not Pass</code>.); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const initialButton = mockedComponent.find('button').text(); const enter3AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 3 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const enter17AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 17 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const userAge3 = await enter3AndClickButton(); const userAge17 = await enter17AndClickButton(); assert(initialButton === 'Submit' && userAge3 === 'You Shall Not Pass' && userAge17 === 'You Shall Not Pass', 'message: When a number of less than 18 is entered into the <code>input</code> element and the <code>button</code> is clicked, the <code>button</code>&apos;s inner text should read <code>You Shall Not Pass</code>.'); }; ",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const initialButton = mockedComponent.find('button').text(); const enter18AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 18 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const enter35AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 35 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const userAge18 = await enter18AndClickButton(); const userAge35 = await enter35AndClickButton(); assert(initialButton === 'Submit' && userAge18 === 'You May Enter' && userAge35 === 'You May Enter', 'message: When a number greater than or equal to 18 is entered into the <code>input</code> element and the <code>button</code> is clicked, the <code>button</code>'s inner text should read <code>You May Enter</code>.); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const initialButton = mockedComponent.find('button').text(); const enter18AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 18 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const enter35AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 35 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const userAge18 = await enter18AndClickButton(); const userAge35 = await enter35AndClickButton(); assert(initialButton === 'Submit' && userAge18 === 'You May Enter' && userAge35 === 'You May Enter', 'message: When a number greater than or equal to 18 is entered into the <code>input</code> element and the <code>button</code> is clicked, the <code>button</code>&apos;s inner text should read <code>You May Enter</code>.'); }; ",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const enter18AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 18 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const changeInputDontClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 5 }}); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const enter10AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 10 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const userAge18 = await enter18AndClickButton(); const changeInput1 = await changeInputDontClickButton(); const userAge10 = await enter10AndClickButton(); const changeInput2 = await changeInputDontClickButton(); assert(userAge18 === 'You May Enter' && changeInput1 === 'Submit' && userAge10 === 'You Shall Not Pass' && changeInput2 === 'Submit', 'message: Once a number has been submitted, and the value of the <code>input</code> is once again changed, the <code>button</code> should return to reading <code>Submit</code>.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const mockedComponent = Enzyme.mount(React.createElement(CheckUserAge)); const enter18AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 18 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const changeInputDontClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 5 }}); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const enter10AndClickButton = () => { mockedComponent.find('input').simulate('change', {target: { value: 10 }}); mockedComponent.find('button').simulate('click'); return waitForIt(() => { mockedComponent.update(); return mockedComponent.find('button').text(); }); }; const userAge18 = await enter18AndClickButton(); const changeInput1 = await changeInputDontClickButton(); const userAge10 = await enter10AndClickButton(); const changeInput2 = await changeInputDontClickButton(); assert(userAge18 === 'You May Enter' && changeInput1 === 'Submit' && userAge10 === 'You Shall Not Pass' && changeInput2 === 'Submit', 'message: Once a number has been submitted, and the value of the <code>input</code> is once again changed, the <code>button</code> should return to reading <code>Submit</code>.'); }; ",
"assert(new RegExp(/(\\s|;)if(\\s|\\()/).test(Enzyme.mount(React.createElement(CheckUserAge)).instance().render.toString()) === false, 'message: Your code should not contain any <code>if/else</code> statements.');" "assert(new RegExp(/(\\s|;)if(\\s|\\()/).test(Enzyme.mount(React.createElement(CheckUserAge)).instance().render.toString()) === false, 'message: Your code should not contain any <code>if/else</code> statements.');"
], ],
@@ -2844,7 +2837,7 @@
"The <code>map</code> array method is a powerful tool that you will use often when working with React. Another method related to <code>map</code> is <code>filter</code>, which filters the contents of an array based on a condition, then returns a new array. For example, if you have an array of users that all have a property <code>online</code> which can be set to <code>true</code> or <code>false</code>, you can filter only those users that are online by writing:", "The <code>map</code> array method is a powerful tool that you will use often when working with React. Another method related to <code>map</code> is <code>filter</code>, which filters the contents of an array based on a condition, then returns a new array. For example, if you have an array of users that all have a property <code>online</code> which can be set to <code>true</code> or <code>false</code>, you can filter only those users that are online by writing:",
"<code>let onlineUsers = users.filter(user => user.online);", "<code>let onlineUsers = users.filter(user => user.online);",
"<hr>", "<hr>",
"In the code editor, <code>MyComponent</code>'s <code>state</code> is initialized with an array of users. Some users are online and some aren't. Filter the array so you see only the users who are online. To do this, first use <code>filter</code> to return a new array containing only the users whose <code>online</code> property is <code>true</code>. Then, in the <code>renderOnline</code> variable, map over the filtered array, and return a <code>li</code> element for each user that contains the text of their <code>username</code>. Be sure to include a unique <code>key</code> as well, like in the last challenges." "In the code editor, <code>MyComponent</code>&apos;s <code>state</code> is initialized with an array of users. Some users are online and some aren't. Filter the array so you see only the users who are online. To do this, first use <code>filter</code> to return a new array containing only the users whose <code>online</code> property is <code>true</code>. Then, in the <code>renderOnline</code> variable, map over the filtered array, and return a <code>li</code> element for each user that contains the text of their <code>username</code>. Be sure to include a unique <code>key</code> as well, like in the last challenges."
], ],
"files": { "files": {
"indexjsx": { "indexjsx": {
@@ -2903,9 +2896,9 @@
}, },
"tests": [ "tests": [
"assert.strictEqual(Enzyme.mount(React.createElement(MyComponent)).find('MyComponent').length, 1, 'message: <code>MyComponent</code> should exist and render to the page.');", "assert.strictEqual(Enzyme.mount(React.createElement(MyComponent)).find('MyComponent').length, 1, 'message: <code>MyComponent</code> should exist and render to the page.');",
"assert(Array.isArray(Enzyme.mount(React.createElement(MyComponent)).state('users')) === true && Enzyme.mount(React.createElement(MyComponent)).state('users').length === 6, \"message: <code>MyComponent</code>'s state should be initialized to an array of six users.\");", "assert(Array.isArray(Enzyme.mount(React.createElement(MyComponent)).state('users')) === true && Enzyme.mount(React.createElement(MyComponent)).state('users').length === 6, \"message: <code>MyComponent</code>&apos;s state should be initialized to an array of six users.\");",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MyComponent)); const users = (bool) => ({users:[ { username: 'Jeff', online: bool }, { username: 'Alan', online: bool }, { username: 'Mary', online: bool }, { username: 'Jim', online: bool }, { username: 'Laura', online: bool } ]}); const result = () => comp.find('li').length; const _1 = result(); const _2 = () => { comp.setState(users(true)); return waitForIt(() => result()) }; const _3 = () => { comp.setState(users(false)); return waitForIt(() => result()) }; const _4 = () => { comp.setState({ users: [] }); return waitForIt(() => result()) }; const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); assert(comp.find('div').length === 1 && comp.find('h1').length === 1 && comp.find('ul').length === 1 && _1 === 4 && _2_val === 5 && _3_val === 0 && _4_val === 0, 'message: <code>MyComponent</code> should return a <code>div</code>, an <code>h1</code>, and then an unordered list containing <code>li</code> elements for every user whose online status is set to <code>true</code>.'); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MyComponent)); const users = (bool) => ({users:[ { username: 'Jeff', online: bool }, { username: 'Alan', online: bool }, { username: 'Mary', online: bool }, { username: 'Jim', online: bool }, { username: 'Laura', online: bool } ]}); const result = () => comp.find('li').length; const _1 = result(); const _2 = () => { comp.setState(users(true)); return waitForIt(() => result()) }; const _3 = () => { comp.setState(users(false)); return waitForIt(() => result()) }; const _4 = () => { comp.setState({ users: [] }); return waitForIt(() => result()) }; const _2_val = await _2(); const _3_val = await _3(); const _4_val = await _4(); assert(comp.find('div').length === 1 && comp.find('h1').length === 1 && comp.find('ul').length === 1 && _1 === 4 && _2_val === 5 && _3_val === 0 && _4_val === 0, 'message: <code>MyComponent</code> should return a <code>div</code>, an <code>h1</code>, and then an unordered list containing <code>li</code> elements for every user whose online status is set to <code>true</code>.'); }; ",
"async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MyComponent)); const users = (bool) => ({users:[ { username: 'Jeff', online: bool }, { username: 'Alan', online: bool }, { username: 'Mary', online: bool }, { username: 'Jim', online: bool }, { username: 'Laura', online: bool } ]}); const ul = () => { comp.setState(users(true)); return waitForIt(() => comp.find('ul').html()) }; const html = await ul(); assert(html === '<ul><li>Jeff</li><li>Alan</li><li>Mary</li><li>Jim</li><li>Laura</li></ul>, 'message: <code>MyComponent</code> should render <code>li</code> elements that contain the username of each online user.); }; ", "async () => { const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250)); const comp = Enzyme.mount(React.createElement(MyComponent)); const users = (bool) => ({users:[ { username: 'Jeff', online: bool }, { username: 'Alan', online: bool }, { username: 'Mary', online: bool }, { username: 'Jim', online: bool }, { username: 'Laura', online: bool } ]}); const ul = () => { comp.setState(users(true)); return waitForIt(() => comp.find('ul').html()) }; const html = await ul(); assert(html === '<ul><li>Jeff</li><li>Alan</li><li>Mary</li><li>Jim</li><li>Laura</li></ul>, 'message: <code>MyComponent</code> should render <code>li</code> elements that contain the username of each online user.'); }; ",
"assert((() => { const ul = Enzyme.mount(React.createElement(MyComponent)).find('ul'); console.log(ul.debug()); const keys = new Set([ ul.childAt(0).key(), ul.childAt(1).key(), ul.childAt(2).key(), ul.childAt(3).key() ]); return keys.size === 4; })(), 'message: Each list item element should have a unique <code>key</code> attribute.');" "assert((() => { const ul = Enzyme.mount(React.createElement(MyComponent)).find('ul'); console.log(ul.debug()); const keys = new Set([ ul.childAt(0).key(), ul.childAt(1).key(), ul.childAt(2).key(), ul.childAt(3).key() ]); return keys.size === 4; })(), 'message: Each list item element should have a unique <code>key</code> attribute.');"
], ],
"solutions": [ "solutions": [

View File

@@ -195,7 +195,7 @@
"tests": [ "tests": [
"assert(loginAction().type === 'LOGIN', 'message: Calling the function <code>loginAction</code> should return an object with <code>type</code> property set to the string <code>LOGIN</code>.');", "assert(loginAction().type === 'LOGIN', 'message: Calling the function <code>loginAction</code> should return an object with <code>type</code> property set to the string <code>LOGIN</code>.');",
"assert(store.getState().login === false, 'message: The store should be initialized with an object with property <code>login</code> set to <code>false</code>.');", "assert(store.getState().login === false, 'message: The store should be initialized with an object with property <code>login</code> set to <code>false</code>.');",
"getUserInput => assert((function() { let noWhiteSpace = getUserInput('index').replace(/ /g,''); return noWhiteSpace.includes('store.dispatch(loginAction())') || noWhiteSpace.includes('store.dispatch({type: \\'LOGIN\\'})') === true })(), 'message: The <code>store.dispatch()</code> method should be used to dispatch an action of type <code>LOGIN</code>.');" "getUserInput => assert((function() { let noWhiteSpace = getUserInput('index').replace(/\\s/g,''); return noWhiteSpace.includes('store.dispatch(loginAction())') || noWhiteSpace.includes('store.dispatch({type: \\'LOGIN\\'})') === true })(), 'message: The <code>store.dispatch()</code> method should be used to dispatch an action of type <code>LOGIN</code>.');"
], ],
"solutions": [ "solutions": [
"const store = Redux.createStore(\n (state = {login: false}) => state\n);\n\nconst loginAction = () => {\n return {\n type: 'LOGIN'\n }\n};\n\n// Dispatch the action here:\nstore.dispatch(loginAction());" "const store = Redux.createStore(\n (state = {login: false}) => state\n);\n\nconst loginAction = () => {\n return {\n type: 'LOGIN'\n }\n};\n\n// Dispatch the action here:\nstore.dispatch(loginAction());"
@@ -381,8 +381,8 @@
"assert((function() { const initialState = store.getState(); store.dispatch(loginUser()); const afterLogin = store.getState(); return initialState.authenticated === false && afterLogin.authenticated === true })(), 'message: Dispatching <code>loginUser</code> should update the <code>login</code> property in the store state to <code>true</code>.');", "assert((function() { const initialState = store.getState(); store.dispatch(loginUser()); const afterLogin = store.getState(); return initialState.authenticated === false && afterLogin.authenticated === true })(), 'message: Dispatching <code>loginUser</code> should update the <code>login</code> property in the store state to <code>true</code>.');",
"assert((function() { store.dispatch(loginUser()); const loggedIn = store.getState(); store.dispatch(logoutUser()); const afterLogout = store.getState(); return loggedIn.authenticated === true && afterLogout.authenticated === false })(), 'message: Dispatching <code>logoutUser</code> should update the <code>login</code> property in the store state to <code>false</code>.');", "assert((function() { store.dispatch(loginUser()); const loggedIn = store.getState(); store.dispatch(logoutUser()); const afterLogout = store.getState(); return loggedIn.authenticated === true && afterLogout.authenticated === false })(), 'message: Dispatching <code>logoutUser</code> should update the <code>login</code> property in the store state to <code>false</code>.');",
"getUserInput => assert((function() { return typeof authReducer === 'function' && getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default') })(), 'message: The <code>authReducer</code> function should handle multiple action types with a switch statement.');", "getUserInput => assert((function() { return typeof authReducer === 'function' && getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default') })(), 'message: The <code>authReducer</code> function should handle multiple action types with a switch statement.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/ /g,''); return (noWhiteSpace.includes('constLOGIN=\\'LOGIN\\'') || noWhiteSpace.includes('constLOGIN=\"LOGIN\"')) && (noWhiteSpace.includes('constLOGOUT=\\'LOGOUT\\'') || noWhiteSpace.includes('constLOGOUT=\"LOGOUT\"')) })(), 'message: <code>LOGIN</code> and <code>LOGOUT</code> should be declared as <code>const</code> values and should be assigned strings of <code>LOGIN</code>and <code>LOGOUT</code>.');", "getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\\s/g,''); return (noWhiteSpace.includes('constLOGIN=\\'LOGIN\\'') || noWhiteSpace.includes('constLOGIN=\"LOGIN\"')) && (noWhiteSpace.includes('constLOGOUT=\\'LOGOUT\\'') || noWhiteSpace.includes('constLOGOUT=\"LOGOUT\"')) })(), 'message: <code>LOGIN</code> and <code>LOGOUT</code> should be declared as <code>const</code> values and should be assigned strings of <code>LOGIN</code>and <code>LOGOUT</code>.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/ /g,''); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })(), 'message: The action creators and the reducer should reference the <code>LOGIN</code> and <code>LOGOUT</code> constants.');" "getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\\s/g,''); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })(), 'message: The action creators and the reducer should reference the <code>LOGIN</code> and <code>LOGOUT</code> constants.');"
], ],
"solutions": [ "solutions": [
"const LOGIN = 'LOGIN';\nconst LOGOUT = 'LOGOUT';\n\nconst defaultState = {\n authenticated: false\n};\n\nconst authReducer = (state = defaultState, action) => {\n\n switch (action.type) {\n\n case LOGIN:\n return {\n authenticated: true\n }\n\n case LOGOUT:\n return {\n authenticated: false\n }\n\n default:\n return state;\n\n }\n\n};\n\nconst store = Redux.createStore(authReducer);\n\nconst loginUser = () => {\n return {\n type: LOGIN\n }\n};\n\nconst logoutUser = () => {\n return {\n type: LOGOUT\n }\n};" "const LOGIN = 'LOGIN';\nconst LOGOUT = 'LOGOUT';\n\nconst defaultState = {\n authenticated: false\n};\n\nconst authReducer = (state = defaultState, action) => {\n\n switch (action.type) {\n\n case LOGIN:\n return {\n authenticated: true\n }\n\n case LOGOUT:\n return {\n authenticated: false\n }\n\n default:\n return state;\n\n }\n\n};\n\nconst store = Redux.createStore(authReducer);\n\nconst loginUser = () => {\n return {\n type: LOGIN\n }\n};\n\nconst logoutUser = () => {\n return {\n type: LOGOUT\n }\n};"
@@ -514,7 +514,7 @@
"assert((function() { const initalState = store.getState().count; store.dispatch({type: INCREMENT}); store.dispatch({type: INCREMENT}); const firstState = store.getState().count; store.dispatch({type: DECREMENT}); const secondState = store.getState().count; return firstState === initalState + 2 && secondState === firstState - 1 })(), 'message: The <code>counterReducer</code> should increment and decrement the <code>state</code>.');", "assert((function() { const initalState = store.getState().count; store.dispatch({type: INCREMENT}); store.dispatch({type: INCREMENT}); const firstState = store.getState().count; store.dispatch({type: DECREMENT}); const secondState = store.getState().count; return firstState === initalState + 2 && secondState === firstState - 1 })(), 'message: The <code>counterReducer</code> should increment and decrement the <code>state</code>.');",
"assert((function() { store.dispatch({type: LOGIN}); const loggedIn = store.getState().auth.authenticated; store.dispatch({type: LOGOUT}); const loggedOut = store.getState().auth.authenticated; return loggedIn === true && loggedOut === false })(), 'message: The <code>authReducer</code> should toggle the <code>state</code> of <code>authenticated</code> between <code>true</code> and <code>false</code>.');", "assert((function() { store.dispatch({type: LOGIN}); const loggedIn = store.getState().auth.authenticated; store.dispatch({type: LOGOUT}); const loggedOut = store.getState().auth.authenticated; return loggedIn === true && loggedOut === false })(), 'message: The <code>authReducer</code> should toggle the <code>state</code> of <code>authenticated</code> between <code>true</code> and <code>false</code>.');",
"assert((function() { const state = store.getState(); return typeof state.auth === 'object' && typeof state.auth.authenticated === 'boolean' && typeof state.count === 'number' })(), 'message: The store <code>state</code> should have two keys: <code>count</code>, which holds a number, and <code>auth</code>, which holds an object. The <code>auth</code> object should have a property of <code>authenticated</code>, which holds a boolean.');", "assert((function() { const state = store.getState(); return typeof state.auth === 'object' && typeof state.auth.authenticated === 'boolean' && typeof state.count === 'number' })(), 'message: The store <code>state</code> should have two keys: <code>count</code>, which holds a number, and <code>auth</code>, which holds an object. The <code>auth</code> object should have a property of <code>authenticated</code>, which holds a boolean.');",
"getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'message: The <code>rootReducer</code> should be a function that combines the <code>counterReducer</code> and the <code>authReducer</code>.');" "getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\\s/g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'message: The <code>rootReducer</code> should be a function that combines the <code>counterReducer</code> and the <code>authReducer</code>.');"
], ],
"solutions": [ "solutions": [
"const INCREMENT = 'INCREMENT';\nconst DECREMENT = 'DECREMENT';\n\nconst counterReducer = (state = 0, action) => {\n switch(action.type) {\n case INCREMENT:\n return state + 1;\n case DECREMENT:\n return state - 1;\n default:\n return state;\n }\n};\n\nconst LOGIN = 'LOGIN';\nconst LOGOUT = 'LOGOUT';\n\nconst authReducer = (state = {authenticated: false}, action) => {\n switch(action.type) {\n case LOGIN:\n return {\n authenticated: true\n }\n case LOGOUT:\n return {\n authenticated: false\n }\n default:\n return state;\n }\n};\n\nconst rootReducer = Redux.combineReducers({\n count: counterReducer,\n auth: authReducer\n});\n\nconst store = Redux.createStore(rootReducer);" "const INCREMENT = 'INCREMENT';\nconst DECREMENT = 'DECREMENT';\n\nconst counterReducer = (state = 0, action) => {\n switch(action.type) {\n case INCREMENT:\n return state + 1;\n case DECREMENT:\n return state - 1;\n default:\n return state;\n }\n};\n\nconst LOGIN = 'LOGIN';\nconst LOGOUT = 'LOGOUT';\n\nconst authReducer = (state = {authenticated: false}, action) => {\n switch(action.type) {\n case LOGIN:\n return {\n authenticated: true\n }\n case LOGOUT:\n return {\n authenticated: false\n }\n default:\n return state;\n }\n};\n\nconst rootReducer = Redux.combineReducers({\n count: counterReducer,\n auth: authReducer\n});\n\nconst store = Redux.createStore(rootReducer);"
@@ -651,7 +651,7 @@
"assert(receivedData('data').type === RECEIVED_DATA, 'message: The <code>receivedData</code> action creator should return an object of type equal to the value of <code>RECEIVED_DATA</code>.');", "assert(receivedData('data').type === RECEIVED_DATA, 'message: The <code>receivedData</code> action creator should return an object of type equal to the value of <code>RECEIVED_DATA</code>.');",
"assert(typeof asyncDataReducer === 'function', 'message: <code>asyncDataReducer</code> should be a function.');", "assert(typeof asyncDataReducer === 'function', 'message: <code>asyncDataReducer</code> should be a function.');",
"assert((function() { const initialState = store.getState(); store.dispatch(requestingData()); const reqState = store.getState(); return initialState.fetching === false && reqState.fetching === true })(), 'message: Dispatching the requestingData action creator should update the store <code>state</code> property of fetching to <code>true</code>.');", "assert((function() { const initialState = store.getState(); store.dispatch(requestingData()); const reqState = store.getState(); return initialState.fetching === false && reqState.fetching === true })(), 'message: Dispatching the requestingData action creator should update the store <code>state</code> property of fetching to <code>true</code>.');",
"assert((function() { const noWhiteSpace = handleAsync.toString().replace(/ /g,''); return noWhiteSpace.includes('dispatch(requestingData())') === true && noWhiteSpace.includes('dispatch(receivedData(data))') === true })(), 'message: Dispatching <code>handleAsync</code> should dispatch the data request action and then dispatch the received data action after a delay.');" "assert((function() { const noWhiteSpace = handleAsync.toString().replace(/\\s/g,''); return noWhiteSpace.includes('dispatch(requestingData())') === true && noWhiteSpace.includes('dispatch(receivedData(data))') === true })(), 'message: Dispatching <code>handleAsync</code> should dispatch the data request action and then dispatch the received data action after a delay.');"
], ],
"solutions": [ "solutions": [
"const REQUESTING_DATA = 'REQUESTING_DATA'\nconst RECEIVED_DATA = 'RECEIVED_DATA'\n\nconst requestingData = () => { return {type: REQUESTING_DATA} }\nconst receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }\n\nconst handleAsync = () => {\n return function(dispatch) {\n dispatch(requestingData());\n setTimeout(function() {\n let data = {\n users: ['Jeff', 'William', 'Alice']\n }\n dispatch(receivedData(data));\n }, 2500);\n }\n};\n\nconst defaultState = {\n fetching: false,\n users: []\n};\n\nconst asyncDataReducer = (state = defaultState, action) => {\n switch(action.type) {\n case REQUESTING_DATA:\n return {\n fetching: true,\n users: []\n }\n case RECEIVED_DATA:\n return {\n fetching: false,\n users: action.users\n }\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(\n asyncDataReducer,\n Redux.applyMiddleware(ReduxThunk.default)\n);" "const REQUESTING_DATA = 'REQUESTING_DATA'\nconst RECEIVED_DATA = 'RECEIVED_DATA'\n\nconst requestingData = () => { return {type: REQUESTING_DATA} }\nconst receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }\n\nconst handleAsync = () => {\n return function(dispatch) {\n dispatch(requestingData());\n setTimeout(function() {\n let data = {\n users: ['Jeff', 'William', 'Alice']\n }\n dispatch(receivedData(data));\n }, 2500);\n }\n};\n\nconst defaultState = {\n fetching: false,\n users: []\n};\n\nconst asyncDataReducer = (state = defaultState, action) => {\n switch(action.type) {\n case REQUESTING_DATA:\n return {\n fetching: true,\n users: []\n }\n case RECEIVED_DATA:\n return {\n fetching: false,\n users: action.users\n }\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(\n asyncDataReducer,\n Redux.applyMiddleware(ReduxThunk.default)\n);"

View File

@@ -8,9 +8,9 @@
"title": "Claim Your Data Visualization Certificate", "title": "Claim Your Data Visualization Certificate",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/N8drT4I.jpg",
"An image of our Data Visualization Certificate", "An image of our Data Visualization Certificate",
"This challenge will give you your verified Data Visualization Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", "This challenge will give you your verified Data Visualization Certificate. Before we issue your certificate, we must verify that you have completed all of our data visualisation projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -20,10 +20,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/UedoV2G.jpg", "//i.imgur.com/BUaEvDo.jpg",
"An image of the text \"Front End Development Certificate requirements\"", "An image of the text \"Data Visualization Certificate requirements\"",
"Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. Click the button below to verify this.", "Let's confirm that you have completed data visualisation projects. Click the button below to verify this.", "#"
"#"
], ],
[ [
"//i.imgur.com/Q5Za9U6.jpg", "//i.imgur.com/Q5Za9U6.jpg",
@@ -36,11 +35,11 @@
{ {
"properties": [ "properties": [
"isHonest", "isHonest",
"isFrontEndCert" "isDataVisCert"
], ],
"apis": [ "apis": [
"/certificate/honest", "/certificate/honest",
"/certificate/verify/front-end" "/certificate/verify/data-visualization"
], ],
"stepIndex": [ "stepIndex": [
1, 1,
@@ -77,10 +76,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces", "title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/N8drT4I.jpg",
"Una imagen que muestra nuestro certificado de Desarrollo de interfaces", "Una imagen que muestra nuestro certificado de Desarrollo de interfaces",
"Este desafío te otorga tu certificado autenticado de Desarrollo de interfaces. Antes de que podamos emitir tu certificado, debemos verificar que has completado todos los desafíos básicos e intermedios de diseño de algoritmos, y todos los proyectos básicos e intermedios de desarrollo de interfaces. También debes aceptar nuestro Juramento de honestidad académica. Pulsa el botón siguiente para iniciar este proceso.", "This challenge will give you your verified Data Visualization Certificate. Before we issue your certificate, we must verify that you have completed all of our data visualisation projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", ""
""
], ],
[ [
"//i.imgur.com/HArFfMN.jpg", "//i.imgur.com/HArFfMN.jpg",
@@ -89,9 +87,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/14F2Van.jpg", "//i.imgur.com/BUaEvDo.jpg",
"Una imagen del texto \"Front End Development Certificate requirements\"", "An image of the text \"Data Visualization Certificate requirements\"",
"Confirmemos que has completado todos nuestros desafíos básicos e intermedios de diseño de algoritmos, y todos nuestros proyectos básicos e intermedios de desarrollo de interfaces. Pulsa el botón siguiente para hacer la verificación.", "Let's confirm that you have completed data visualisation projects. Click the button below to verify this.", "Confirmemos que has completado todos nuestros desafíos básicos e intermedios de diseño de algoritmos, y todos nuestros proyectos básicos e intermedios de desarrollo de interfaces. Pulsa el botón siguiente para hacer la verificación.",
"#" "#"
], ],
[ [

View File

@@ -8,9 +8,9 @@
"title": "Claim Your APIs and Microservices Certificate", "title": "Claim Your APIs and Microservices Certificate",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/gfH7j5B.jpg",
"An image of our APIs and Microservices Certificate", "An image of our APIs and Microservices Certificate",
"This challenge will give you your verified APIs and Microservices Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", "This challenge will give you your verified APIs and Microservices Certificate. Before we issue your certificate, we must verify that you have completed all of our apis and microservices projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -20,9 +20,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/UedoV2G.jpg", "//i.imgur.com/IBTfUzO.jpg",
"An image of the text \"Front End Development Certificate requirements\"", "An image of the text \"APIs and Microservices Certificate requirements\"",
"Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. Click the button below to verify this.", "Let's confirm that you have completed all of our apis and microservices projects. Click the button below to verify this.",
"#" "#"
], ],
[ [
@@ -36,11 +36,11 @@
{ {
"properties": [ "properties": [
"isHonest", "isHonest",
"isFrontEndCert" "isApisMicroservicesCert"
], ],
"apis": [ "apis": [
"/certificate/honest", "/certificate/honest",
"/certificate/verify/front-end" "/certificate/verify/apis-microservices"
], ],
"stepIndex": [ "stepIndex": [
1, 1,
@@ -77,9 +77,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces", "title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/gfH7j5B.jpg",
"Una imagen que muestra nuestro certificado de Desarrollo de interfaces", "Una imagen que muestra nuestro certificado de Desarrollo de interfaces",
"Este desafío te otorga tu certificado autenticado de Desarrollo de interfaces. Antes de que podamos emitir tu certificado, debemos verificar que has completado todos los desafíos básicos e intermedios de diseño de algoritmos, y todos los proyectos básicos e intermedios de desarrollo de interfaces. También debes aceptar nuestro Juramento de honestidad académica. Pulsa el botón siguiente para iniciar este proceso.", "This challenge will give you your verified APIs and Microservices Certificate. Before we issue your certificate, we must verify that you have completed all of our apis and microservices projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -89,9 +89,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/14F2Van.jpg", "//i.imgur.com/IBTfUzO.jpg",
"Una imagen del texto \"Front End Development Certificate requirements\"", "An image of the text \"APIs and Microservices Certificate requirements\"",
"Confirmemos que has completado todos nuestros desafíos básicos e intermedios de diseño de algoritmos, y todos nuestros proyectos básicos e intermedios de desarrollo de interfaces. Pulsa el botón siguiente para hacer la verificación.", "Let's confirm that you have completed all of our apis and microservices projects. Click the button below to verify this.",
"#" "#"
], ],
[ [

View File

@@ -8,9 +8,9 @@
"title": "Claim Your Information Security and Quality Assurance Certificate", "title": "Claim Your Information Security and Quality Assurance Certificate",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/YhKzGLb.jpg",
"An image of our Information Security and Quality Assurance Certificate", "An image of our Information Security and Quality Assurance Certificate",
"This challenge will give you your verified Information Security and Quality Assurance Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.", "This challenge will give you your verified Information Security and Quality Assurance Certificate. Before we issue your certificate, we must verify that you have completed all of our information security and quality assurance projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -20,9 +20,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/UedoV2G.jpg", "//i.imgur.com/TM4KGfb.jpg",
"An image of the text \"Front End Development Certificate requirements\"", "An image of the text \"Information Security and Quality Assurance Certificate requirements\"",
"Let's confirm that you have completed all of our basic and intermediate algorithm scripting challenges, and all our basic, intermediate, and advanced front end development projects. Click the button below to verify this.", "Let's confirm that you have completed all of our information security and quality assurance projects. Click the button below to verify this.",
"#" "#"
], ],
[ [
@@ -36,11 +36,11 @@
{ {
"properties": [ "properties": [
"isHonest", "isHonest",
"isFrontEndCert" "isInfosecQaCert"
], ],
"apis": [ "apis": [
"/certificate/honest", "/certificate/honest",
"/certificate/verify/front-end" "/certificate/verify/information-security-quality-assurance"
], ],
"stepIndex": [ "stepIndex": [
1, 1,
@@ -77,9 +77,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces", "title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [ "description": [
[ [
"//i.imgur.com/k8btNUB.jpg", "//i.imgur.com/YhKzGLb.jpg",
"Una imagen que muestra nuestro certificado de Desarrollo de interfaces", "Una imagen que muestra nuestro certificado de Desarrollo de interfaces",
"Este desafío te otorga tu certificado autenticado de Desarrollo de interfaces. Antes de que podamos emitir tu certificado, debemos verificar que has completado todos los desafíos básicos e intermedios de diseño de algoritmos, y todos los proyectos básicos e intermedios de desarrollo de interfaces. También debes aceptar nuestro Juramento de honestidad académica. Pulsa el botón siguiente para iniciar este proceso.", "This challenge will give you your verified Information Security and Quality Assurance Certificate. Before we issue your certificate, we must verify that you have completed all of our information security and quality assurance projects. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
"" ""
], ],
[ [
@@ -89,9 +89,9 @@
"#" "#"
], ],
[ [
"//i.imgur.com/14F2Van.jpg", "//i.imgur.com/TM4KGfb.jpg",
"Una imagen del texto \"Front End Development Certificate requirements\"", "An image of the text \"Information Security and Quality Assurance Certificate requirements\"",
"Confirmemos que has completado todos nuestros desafíos básicos e intermedios de diseño de algoritmos, y todos nuestros proyectos básicos e intermedios de desarrollo de interfaces. Pulsa el botón siguiente para hacer la verificación.", "Let's confirm that you have completed all of our information security and quality assurance projects. Click the button below to verify this.",
"#" "#"
], ],
[ [

View File

@@ -4,94 +4,6 @@
"time": "", "time": "",
"helpRoom": "HelpJavaScript", "helpRoom": "HelpJavaScript",
"challenges": [ "challenges": [
{
"id": "aff0395860f5d3034dc0bfc9",
"title": "Validate US Telephone Numbers",
"description": [
"Return <code>true</code> if the passed string looks like a valid US phone number.",
"The user may fill out the form field any way they choose as long as it has the format of a valid US number. The following are examples of valid formats for US numbers (refer to the tests below for other variants):",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"For this challenge you will be presented with a string such as <code>800-692-7753</code> or <code>8oo-six427676;laskdjf</code>. Your job is to validate or reject the US phone number based on any combination of the formats provided above. The area code is required. If the country code is provided, you must confirm that the country code is <code>1</code>. Return <code>true</code> if the string is a valid US phone number; otherwise return <code>false</code>.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code."
],
"challengeSeed": [
"function telephoneCheck(str) {",
" // Good luck!",
" return true;",
"}",
"",
"telephoneCheck(\"555-555-5555\");"
],
"solutions": [
"var re = /^([+]?1[\\s]?)?((?:[(](?:[2-9]1[02-9]|[2-9][02-8][0-9])[)][\\s]?)|(?:(?:[2-9]1[02-9]|[2-9][02-8][0-9])[\\s.-]?)){1}([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2}[\\s.-]?){1}([0-9]{4}){1}$/;\n\nfunction telephoneCheck(str) {\n return re.test(str);\n}\n\ntelephoneCheck(\"555-555-5555\");"
],
"tests": [
"assert(typeof telephoneCheck(\"555-555-5555\") === \"boolean\", 'message: <code>telephoneCheck(\"555-555-5555\")</code> should return a boolean.');",
"assert(telephoneCheck(\"1 555-555-5555\") === true, 'message: <code>telephoneCheck(\"1 555-555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"1 (555) 555-5555\") === true, 'message: <code>telephoneCheck(\"1 (555) 555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"5555555555\") === true, 'message: <code>telephoneCheck(\"5555555555\")</code> should return true.');",
"assert(telephoneCheck(\"555-555-5555\") === true, 'message: <code>telephoneCheck(\"555-555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"(555)555-5555\") === true, 'message: <code>telephoneCheck(\"(555)555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"1(555)555-5555\") === true, 'message: <code>telephoneCheck(\"1(555)555-5555\")</code> should return true.');",
"assert(telephoneCheck(\"555-5555\") === false, 'message: <code>telephoneCheck(\"555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"5555555\") === false, 'message: <code>telephoneCheck(\"5555555\")</code> should return false.');",
"assert(telephoneCheck(\"1 555)555-5555\") === false, 'message: <code>telephoneCheck(\"1 555)555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"1 555 555 5555\") === true, 'message: <code>telephoneCheck(\"1 555 555 5555\")</code> should return true.');",
"assert(telephoneCheck(\"1 456 789 4444\") === true, 'message: <code>telephoneCheck(\"1 456 789 4444\")</code> should return true.');",
"assert(telephoneCheck(\"123**&!!asdf#\") === false, 'message: <code>telephoneCheck(\"123**&!!asdf#\")</code> should return false.');",
"assert(telephoneCheck(\"55555555\") === false, 'message: <code>telephoneCheck(\"55555555\")</code> should return false.');",
"assert(telephoneCheck(\"(6054756961)\") === false, 'message: <code>telephoneCheck(\"(6054756961)\")</code> should return false');",
"assert(telephoneCheck(\"2 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"2 (757) 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"0 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"0 (757) 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"-1 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"-1 (757) 622-7382\")</code> should return false');",
"assert(telephoneCheck(\"2 757 622-7382\") === false, 'message: <code>telephoneCheck(\"2 757 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"10 (757) 622-7382\") === false, 'message: <code>telephoneCheck(\"10 (757) 622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"27576227382\") === false, 'message: <code>telephoneCheck(\"27576227382\")</code> should return false.');",
"assert(telephoneCheck(\"(275)76227382\") === false, 'message: <code>telephoneCheck(\"(275)76227382\")</code> should return false.');",
"assert(telephoneCheck(\"2(757)6227382\") === false, 'message: <code>telephoneCheck(\"2(757)6227382\")</code> should return false.');",
"assert(telephoneCheck(\"2(757)622-7382\") === false, 'message: <code>telephoneCheck(\"2(757)622-7382\")</code> should return false.');",
"assert(telephoneCheck(\"555)-555-5555\") === false, 'message: <code>telephoneCheck(\"555)-555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"(555-555-5555\") === false, 'message: <code>telephoneCheck(\"(555-555-5555\")</code> should return false.');",
"assert(telephoneCheck(\"(555)5(55?)-5555\") === false, 'message: <code>telephoneCheck(\"(555)5(55?)-5555\")</code> should return false.');"
],
"type": "bonfire",
"MDNlinks": [
"RegExp"
],
"challengeType": 5,
"translations": {
"es": {
"title": "Valida Números Telefónicos de los EEUU",
"description": [
"Haz que la función devuelva true (verdadero) si el texto introducido parece un número válido en los EEUU.",
"El usuario debe llenar el campo del formulario de la forma que desee siempre y cuando tenga el formato de un número válido en los EEUU. Los números mostrados a continuación tienen formatos válidos en los EEUU:",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"Para esta prueba se te presentará una cadena de texto como por ejemplo: <code>800-692-7753</code> o <code>8oo-six427676;laskdjf</code>. Tu trabajo consiste en validar o rechazar el número telefónico tomando como base cualquier combinación de los formatos anteriormente presentados. El código de área es requrido. Si el código de país es provisto, debes confirmar que este es <code>1</code>. La función debe devolver true si la cadena de texto es un número telefónico válido en los EEUU; de lo contrario, debe devolver false.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"it": {
"title": "Verifica i numeri telefonici degli Stati Uniti",
"description": [
"Ritorna <code>true</code> se la stringa passata come argomento è un numero valido negli Stati Uniti.",
"L'utente può digitare qualunque stringa nel campo di inserimento, purchè sia un numero di telefono valido negli Stati Uniti. Qui sotto alcuni esempi di numeri di telefono validi negli Stati Uniti (fai riferimento ai test per le altre varianti):",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"In questo problema ti saranno presentate delle stringe come <code>800-692-7753</code> o <code>8oo-six427676;laskdjf</code>. Il tuo obiettivo è di validare o rigettare il numero di telefono basato su una qualunque combinazione dei formati specificati sopra. Il prefisso di zona è obbligatorio. Se il prefisso nazionale è presente, devi confermare che corrisponda a <code>1</code>. Ritorna <code>true</code> se la stringa è un numero di telefono valido negli Stati Uniti; altrimenti ritorna <code>false</code>.",
"Ricorda di usare <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leggi-Cerca-Chiedi</a> se rimani bloccato. Prova a programmare in coppia. Scrivi il codice da te."
]
},
"pt-br": {
"title": "Valida números telefônicos dos EUA",
"description": [
"Retorna <code>true</code> se a string passada é um número telefônico válido nos EUA.",
"O usuário pode preencher o campo de qualquer maneira com tanto que seja um número válido nos EUA. Os seguintes exemplos são formatos válidos para números de telefone nos EUA (baseie-se nos testes abaixo para outras variações):",
"<blockquote>555-555-5555\n(555)555-5555\n(555) 555-5555\n555 555 5555\n5555555555\n1 555 555 5555</blockquote>",
"Para esse desafio será dado a você uma string como <code>800-692-7753</code> ou <code>8oo-six427676;laskdjf</code>. Seu trabalho é validar ou rejeitar o número de telefone dos EUA baseado nos exmplos de formatos fornecidos acima. O código de área é obrigatório. Se o código do país for fornecido, você deve confirmar que o código do país é <code>1</code>. Retorne <code>true</code> se a string é um número válido nos EUA; caso contrário retorne <code>false</code>.",
"Lembre-se de usar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Ler-Procurar-Perguntar</a> se você ficar preso. Tente programar em par. Escreva seu próprio código."
]
}
}
},
{ {
"id": "a3f503de51cf954ede28891d", "id": "a3f503de51cf954ede28891d",
"title": "Symmetric Difference", "title": "Symmetric Difference",
@@ -159,90 +71,6 @@
} }
} }
}, },
{
"id": "aa2e6f85cab2ab736c9a9b24",
"title": "Exact Change",
"description": [
"Design a cash register drawer function <code>checkCashRegister()</code> that accepts purchase price as the first argument (<code>price</code>), payment as the second argument (<code>cash</code>), and cash-in-drawer (<code>cid</code>) as the third argument.",
"<code>cid</code> is a 2D array listing available currency.",
"Return the string <code>\"Insufficient Funds\"</code> if cash-in-drawer is less than the change due or if you cannot return the exact change. Return the string <code>\"Closed\"</code> if cash-in-drawer is equal to the change due.",
"Otherwise, return change in coin and bills, sorted in highest to lowest order.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck. Try to pair program. Write your own code.",
"<table class='table table-striped'><tr><th>Currency Unit</th><th>Amount</th></tr><tr><td>Penny</td><td>$0.01 (PENNY)</td></tr><tr><td>Nickel</td><td>$0.05 (NICKEL)</td></tr><tr><td>Dime</td><td>$0.1 (DIME)</td></tr><tr><td>Quarter</td><td>$0.25 (QUARTER)</td></tr><tr><td>Dollar</td><td>$1 (DOLLAR)</td></tr><tr><td>Five Dollars</td><td>$5 (FIVE)</td></tr><tr><td>Ten Dollars</td><td>$10 (TEN)</td></tr><tr><td>Twenty Dollars</td><td>$20 (TWENTY)</td></tr><tr><td>One-hundred Dollars</td><td>$100 (ONE HUNDRED)</td></tr></table>"
],
"challengeSeed": [
"function checkCashRegister(price, cash, cid) {",
" var change;",
" // Here is your change, ma'am.",
" return change;",
"}",
"",
"// Example cash-in-drawer array:",
"// [[\"PENNY\", 1.01],",
"// [\"NICKEL\", 2.05],",
"// [\"DIME\", 3.1],",
"// [\"QUARTER\", 4.25],",
"// [\"ONE\", 90],",
"// [\"FIVE\", 55],",
"// [\"TEN\", 20],",
"// [\"TWENTY\", 60],",
"// [\"ONE HUNDRED\", 100]]",
"",
"checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]);"
],
"solutions": [
"var denom = [\n\t{ name: 'ONE HUNDRED', val: 100},\n\t{ name: 'TWENTY', val: 20},\n\t{ name: 'TEN', val: 10},\n\t{ name: 'FIVE', val: 5},\n\t{ name: 'ONE', val: 1},\n\t{ name: 'QUARTER', val: 0.25},\n\t{ name: 'DIME', val: 0.1},\n\t{ name: 'NICKEL', val: 0.05},\n\t{ name: 'PENNY', val: 0.01}\n];\n\nfunction checkCashRegister(price, cash, cid) {\n var change = cash - price;\n var register = cid.reduce(function(acc, curr) {\n acc.total += curr[1];\n acc[curr[0]] = curr[1];\n return acc;\n }, {total: 0});\n if(register.total === change) {\n return 'Closed';\n }\n if(register.total < change) {\n return 'Insufficient Funds';\n }\n var change_arr = denom.reduce(function(acc, curr) {\n var value = 0;\n while(register[curr.name] > 0 && change >= curr.val) {\n change -= curr.val;\n register[curr.name] -= curr.val;\n value += curr.val;\n change = Math.round(change * 100) / 100;\n }\n if(value > 0) {\n acc.push([ curr.name, value ]);\n }\n return acc;\n }, []);\n if(change_arr.length < 1 || change > 0) {\n return \"Insufficient Funds\";\n }\n return change_arr;\n}"
],
"tests": [
"assert.isArray(checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]), 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return an array.');",
"assert.isString(checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return a string.');",
"assert.isString(checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return a string.');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]), [[\"QUARTER\", 0.5]], 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>[[\"QUARTER\", 0.5]]</code>.');",
"assert.deepEqual(checkCashRegister(3.26, 100, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]]), [[\"TWENTY\", 60], [\"TEN\", 20], [\"FIVE\", 15], [\"ONE\", 1], [\"QUARTER\", 0.5], [\"DIME\", 0.2], [\"PENNY\", 0.04]], 'message: <code>checkCashRegister(3.26, 100, [[\"PENNY\", 1.01], [\"NICKEL\", 2.05], [\"DIME\", 3.1], [\"QUARTER\", 4.25], [\"ONE\", 90], [\"FIVE\", 55], [\"TEN\", 20], [\"TWENTY\", 60], [\"ONE HUNDRED\", 100]])</code> should return <code>[[\"TWENTY\", 60], [\"TEN\", 20], [\"FIVE\", 15], [\"ONE\", 1], [\"QUARTER\", 0.5], [\"DIME\", 0.2], [\"PENNY\", 0.04]]</code>.');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), \"Insufficient Funds\", 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return \"Insufficient Funds\".');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 1], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), \"Insufficient Funds\", 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.01], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 1], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return \"Insufficient Funds\".');",
"assert.deepEqual(checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]]), \"Closed\", 'message: <code>checkCashRegister(19.5, 20, [[\"PENNY\", 0.5], [\"NICKEL\", 0], [\"DIME\", 0], [\"QUARTER\", 0], [\"ONE\", 0], [\"FIVE\", 0], [\"TEN\", 0], [\"TWENTY\", 0], [\"ONE HUNDRED\", 0]])</code> should return \"Closed\".');"
],
"type": "bonfire",
"MDNlinks": [
"Global Object",
"Floating Point Guide"
],
"challengeType": 5,
"translations": {
"es": {
"title": "Cambio Exacto",
"description": [
"Crea una función que simule una caja registradora que acepte el precio de compra como el primer argumento, la cantidad recibida como el segundo argumento, y la cantidad de dinero disponible en la registradora (cid) como tercer argumento",
"cid es un arreglo bidimensional que lista la cantidad de dinero disponible",
"La función debe devolver la cadena de texto \"Insufficient Funds\" si el cid es menor al cambio requerido. También debe devolver \"Closed\" si el cid es igual al cambio",
"De no ser el caso, devuelve el cambio en monedas y billetes, ordenados de mayor a menor denominación.",
"Recuerda utilizar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> si te sientes atascado. Intenta programar en pareja. Escribe tu propio código."
]
},
"it": {
"title": "Cambio Esatto",
"description": [
"Scrivi una funzione che simuli un registro di cassa chiamata <code>checkCashRegister()</code> che accetti il prezzo degli articoli come primo argomento (<code>price</code>), la somma pagata (<code>cash</code>), e la somma disponibile nel registratore di cassa (<code>cid</code>) come terzo argomento.",
"<code>cid</code> è un array a due dimensioni che contiene la quantità di monete e banconote disponibili.",
"Ritorna la stringa <code>\"Insufficient Funds\"</code> se la quantità di denaro disponibile nel registratore di cassa non è abbastanza per restituire il resto. Ritorna la stringa <code>\"Closed\"</code> se il denaro disponibile è esattamente uguale al resto.",
"Altrimenti, ritorna il resto in monete e banconote, ordinate da quelle con valore maggiore a quelle con valore minore.",
"Ricorda di usare <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Leggi-Cerca-Chiedi</a> se rimani bloccato. Prova a programmare in coppia. Scrivi il codice da te."
]
},
"pt-br": {
"title": "Troco Exato",
"description": [
"Crie uma função que simula uma caixa registradora chamada <code>checkCashRegister()</code> e aceita o valor da compra como primeiro argumento (<code>price</code>), pagamento como segundo argumento (<code>cash</code>), e o dinheiro na caixa registradora (<code>cid</code>) como terceiro argumento.",
"<code>cid</code> é uma matriz bidimensional que lista o dinheiro disponível.",
"Retorne a string <code>\"Insufficient Funds\"</code> se o dinheiro na caixa registradora é menor do que o troco ou se não é possível retornar o troco exato. Retorne a string <code>\"Closed\"</code> se o dinheiro na caixa é igual ao troco.",
"Case cotrário, retorne o troco em moedas e notas, ordenado do maior para menor.",
"Lembre-se de usar <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Ler-Procurar-Perguntar</a> se você ficar preso. Tente programar em par. Escreva seu próprio código.",
"<table class='table table-striped'><tr><th>Currency Unit</th><th>Amount</th></tr><tr><td>Penny</td><td>$0.01 (PENNY)</td></tr><tr><td>Nickel</td><td>$0.05 (NICKEL)</td></tr><tr><td>Dime</td><td>$0.1 (DIME)</td></tr><tr><td>Quarter</td><td>$0.25 (QUARTER)</td></tr><tr><td>Dollar</td><td>$1 (DOLLAR)</td></tr><tr><td>Five Dollars</td><td>$5 (FIVE)</td></tr><tr><td>Ten Dollars</td><td>$10 (TEN)</td></tr><tr><td>Twenty Dollars</td><td>$20 (TWENTY)</td></tr><tr><td>One-hundred Dollars</td><td>$100 (ONE HUNDRED)</td></tr></table>"
]
}
}
},
{ {
"id": "a56138aff60341a09ed6c480", "id": "a56138aff60341a09ed6c480",
"title": "Inventory Update", "title": "Inventory Update",

View File

@@ -372,7 +372,7 @@
" // this method will remove an element from a set", " // this method will remove an element from a set",
" this.remove = function(element) {", " this.remove = function(element) {",
" if(this.has(element)){", " if(this.has(element)){",
" index = collection.indexOf(element);", " var index = collection.indexOf(element);",
" collection.splice(index,1);", " collection.splice(index,1);",
" return true;", " return true;",
" }", " }",
@@ -424,7 +424,7 @@
" // this method will remove an element from a set", " // this method will remove an element from a set",
" this.remove = function(element) {", " this.remove = function(element) {",
" if(this.has(element)){", " if(this.has(element)){",
" index = collection.indexOf(element);", " var index = collection.indexOf(element);",
" collection.splice(index,1);", " collection.splice(index,1);",
" return true;", " return true;",
" }", " }",
@@ -481,7 +481,7 @@
" // this method will remove an element from a set", " // this method will remove an element from a set",
" this.remove = function(element) {", " this.remove = function(element) {",
" if(this.has(element)){", " if(this.has(element)){",
" index = collection.indexOf(element);", " var index = collection.indexOf(element);",
" collection.splice(index,1);", " collection.splice(index,1);",
" return true;", " return true;",
" }", " }",
@@ -550,7 +550,7 @@
" // this method will remove an element from a set", " // this method will remove an element from a set",
" this.remove = function(element) {", " this.remove = function(element) {",
" if(this.has(element)){", " if(this.has(element)){",
" index = collection.indexOf(element);", " var index = collection.indexOf(element);",
" collection.splice(index,1);", " collection.splice(index,1);",
" return true;", " return true;",
" }", " }",
@@ -630,7 +630,7 @@
" // this method will remove an element from a set", " // this method will remove an element from a set",
" this.remove = function(element) {", " this.remove = function(element) {",
" if(this.has(element)){", " if(this.has(element)){",
" index = collection.indexOf(element);", " var index = collection.indexOf(element);",
" collection.splice(index,1);", " collection.splice(index,1);",
" return true;", " return true;",
" }", " }",

View File

@@ -30,7 +30,7 @@
"<pre><code class='language-javascript'>'ABC'</code></pre>" "<pre><code class='language-javascript'>'ABC'</code></pre>"
], ],
"answer": 0, "answer": 0,
"explanation": "The map function will return a new array with each element equal to the old element ran through a callback function. Our callback function takes our original element, changes it to a upper case, and then wraps it in an array; thus, leaving us with <code>[['A', 'B', 'C']]</code>" "explanation": "The map function will return a new array with each element equal to the old element ran through a callback function. Our callback function takes our original element, changes it to a upper case, and then wraps it in an array; thus, leaving us with <code>[['A'], ['B'], ['C']]</code>"
}, },
{ {
"subtitle": "Maps on Maps", "subtitle": "Maps on Maps",

View File

@@ -9,7 +9,8 @@
"title": "Show the Local Weather", "title": "Show the Local Weather",
"description": [ "description": [
"<strong>Objective:</strong> Build a <a href='https://codepen.io' target='_blank'>CodePen.io</a> app that is functionally similar to this: <a href='https://codepen.io/freeCodeCamp/full/bELRjV' target='_blank'>https://codepen.io/freeCodeCamp/full/bELRjV</a>.", "<strong>Objective:</strong> Build a <a href='https://codepen.io' target='_blank'>CodePen.io</a> app that is functionally similar to this: <a href='https://codepen.io/freeCodeCamp/full/bELRjV' target='_blank'>https://codepen.io/freeCodeCamp/full/bELRjV</a>.",
"Fulfill the below <a href='https://en.wikipedia.org/wiki/User_story' target='_blank'>user stories</a>. Use whichever libraries or APIs you need. Give it your own personal style.", "<strong>Rule #1:</strong> Don't look at the example project's code. Figure it out for yourself.",
"<strong>Rule #2:</strong> Fulfill the below <a href='https://en.wikipedia.org/wiki/User_story' target='_blank'>user stories</a>. Use whichever libraries or APIs you need. Give it your own personal style.",
"<strong>User Story:</strong> I can see the weather in my current location.", "<strong>User Story:</strong> I can see the weather in my current location.",
"<strong>User Story:</strong> I can see a different icon or background image (e.g. snowy mountain, hot desert) depending on the weather.", "<strong>User Story:</strong> I can see a different icon or background image (e.g. snowy mountain, hot desert) depending on the weather.",
"<strong>User Story:</strong> I can push a button to toggle between Fahrenheit and Celsius.", "<strong>User Story:</strong> I can push a button to toggle between Fahrenheit and Celsius.",
@@ -675,6 +676,29 @@
], ],
"isRequired": false, "isRequired": false,
"titleEs": "Crea un clon de Pinterest" "titleEs": "Crea un clon de Pinterest"
},
{
"id": "5a4b7fcdb66f799f199e11db",
"title": "Build a Pong Game",
"description": [
"<strong>Objective:</strong> Build a <a href='https://codepen.io' target='_blank'>CodePen.io</a> app that is functionally similar to this: <a href='https://codepen.io/satyamdev/full/pdMmBp' target='_blank'>https://codepen.io/satyamdev/full/pdMmBp</a>.",
"<strong>Rule #1:</strong> Don't look at the example project's code. Figure it out for yourself.",
"<strong>Rule #2:</strong> Fulfill the below <a href='https://en.wikipedia.org/wiki/User_story' target='_blank'>user stories</a>. Use whichever libraries or APIs you need. Give it your own personal style.",
"<strong>User Story:</strong> I can control a paddle.",
"<strong>User Story:</strong> The computer can control the other paddle.",
"<strong>User Story:</strong> The computer's paddle is unbeatable. It should never miss the ball.",
"<strong>User Story:</strong> The game keeps track of the player and computer's score.",
"Remember to use <a href='http://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> if you get stuck.",
"When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen.",
"You can get feedback on your project by sharing it with your friends on Facebook."
],
"challengeSeed": [
""
],
"tests": [],
"type": "zipline",
"challengeType": 3,
"isRequired": false
} }
] ]
} }

View File

@@ -1,28 +1,33 @@
/* eslint-disable no-process-exit */ /* eslint-disable no-process-exit */
require('babel-register'); require('babel-register');
require('dotenv').load(); require('dotenv').load();
var adler32 = require('adler32'); const adler32 = require('adler32');
var Rx = require('rx'), const Rx = require('rx');
_ = require('lodash'), const _ = require('lodash');
utils = require('../server/utils'), const utils = require('../server/utils');
getChallenges = require('./getChallenges'), const getChallenges = require('./getChallenges');
app = require('../server/server'); const app = require('../server/server');
const createDebugger = require('debug');
const log = createDebugger('fcc:seed');
// force logger to always output
// this may be brittle
log.enabled = true;
var dasherize = utils.dasherize; const dasherize = utils.dasherize;
var nameify = utils.nameify; const nameify = utils.nameify;
var Observable = Rx.Observable; const Observable = Rx.Observable;
var Challenge = app.models.Challenge; const Challenge = app.models.Challenge;
var destroyChallenges = const destroyChallenges =
Observable.fromNodeCallback(Challenge.destroyAll, Challenge); Observable.fromNodeCallback(Challenge.destroyAll, Challenge);
var createChallenges = const createChallenges =
Observable.fromNodeCallback(Challenge.create, Challenge); Observable.fromNodeCallback(Challenge.create, Challenge);
var Block = app.models.Block; const Block = app.models.Block;
var destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block); const destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
var createBlocks = Observable.fromNodeCallback(Block.create, Block); const createBlocks = Observable.fromNodeCallback(Block.create, Block);
const arrToString = arr => const arrToString = arr =>
Array.isArray(arr) ? arr.join('\n') : _.toString(arr); Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
@@ -33,28 +38,28 @@ Observable.combineLatest(
.last() .last()
.flatMap(function() { return Observable.from(getChallenges()); }) .flatMap(function() { return Observable.from(getChallenges()); })
.flatMap(function(challengeSpec) { .flatMap(function(challengeSpec) {
var order = challengeSpec.order; const order = challengeSpec.order;
var blockName = challengeSpec.name; const blockName = challengeSpec.name;
var superBlock = challengeSpec.superBlock; const superBlock = challengeSpec.superBlock;
var superOrder = challengeSpec.superOrder; const superOrder = challengeSpec.superOrder;
var isBeta = !!challengeSpec.isBeta; const isBeta = !!challengeSpec.isBeta;
var isComingSoon = !!challengeSpec.isComingSoon; const isComingSoon = !!challengeSpec.isComingSoon;
var fileName = challengeSpec.fileName; const fileName = challengeSpec.fileName;
var helpRoom = challengeSpec.helpRoom || 'Help'; const helpRoom = challengeSpec.helpRoom || 'Help';
var time = challengeSpec.time || 'N/A'; const time = challengeSpec.time || 'N/A';
var isLocked = !!challengeSpec.isLocked; const isLocked = !!challengeSpec.isLocked;
var message = challengeSpec.message; const message = challengeSpec.message;
var required = challengeSpec.required || []; const required = challengeSpec.required || [];
var template = challengeSpec.template; const template = challengeSpec.template;
console.log('parsed %s successfully', blockName); log('parsed %s successfully', blockName);
// challenge file has no challenges... // challenge file has no challenges...
if (challengeSpec.challenges.length === 0) { if (challengeSpec.challenges.length === 0) {
return Rx.Observable.just([{ block: 'empty ' + blockName }]); return Rx.Observable.just([{ block: 'empty ' + blockName }]);
} }
var block = { const block = {
title: blockName, title: blockName,
name: nameify(blockName), name: nameify(blockName),
dashedName: dasherize(blockName), dashedName: dasherize(blockName),
@@ -68,7 +73,7 @@ Observable.combineLatest(
return createBlocks(block) return createBlocks(block)
.map(block => { .map(block => {
console.log('successfully created %s block', block.name); log('successfully created %s block', block.name);
return challengeSpec.challenges return challengeSpec.challenges
.map(function(challenge, index) { .map(function(challenge, index) {
@@ -123,11 +128,11 @@ Observable.combineLatest(
}) })
.subscribe( .subscribe(
function(challenges) { function(challenges) {
console.log('%s successfully saved', challenges[0].block); log('%s successfully saved', challenges[0].block);
}, },
function(err) { throw err; }, function(err) { throw err; },
function() { function() {
console.log('challenge seed completed'); log('challenge seed completed');
process.exit(0); process.exit(0);
} }
); );

View File

@@ -0,0 +1,15 @@
import { Observable } from 'rx';
export default function extendEmail(app) {
const { AccessToken, Email } = app.models;
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
AccessToken.findOne$ = Observable.fromNodeCallback(
AccessToken.findOne.bind(AccessToken)
);
AccessToken.prototype.validate$ = Observable.fromNodeCallback(
AccessToken.prototype.validate
);
AccessToken.prototype.destroy$ = Observable.fromNodeCallback(
AccessToken.prototype.destroy
);
}

View File

@@ -1,6 +0,0 @@
import { Observable } from 'rx';
export default function extendEmail(app) {
const { Email } = app.models;
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
}

View File

@@ -1,4 +1,229 @@
import _ from 'lodash';
import { Observable } from 'rx';
import dedent from 'dedent';
// import debugFactory from 'debug';
import { isEmail } from 'validator';
import { check, validationResult } from 'express-validator/check';
import { ifUserRedirectTo } from '../utils/middleware';
import {
wrapHandledError,
createValidatorErrorFormatter
} from '../utils/create-handled-error.js';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
// const debug = debugFactory('fcc:boot:auth');
if (isSignUpDisabled) {
console.log('fcc:boot:auth - Sign up is disabled');
}
module.exports = function enableAuthentication(app) { module.exports = function enableAuthentication(app) {
// enable authentication // enable loopback access control authentication. see:
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
app.enableAuth(); app.enableAuth();
const ifUserRedirect = ifUserRedirectTo();
const router = app.loopback.Router();
const api = app.loopback.Router();
const { AuthToken, User } = app.models;
router.get('/login', (req, res) => res.redirect(301, '/signin'));
router.get('/logout', (req, res) => res.redirect(301, '/signout'));
function getEmailSignin(req, res) {
if (isSignUpDisabled) {
return res.render('account/beta', {
title: 'New sign ups are disabled'
});
}
return res.render('account/email-signin', {
title: 'Sign in to freeCodeCamp using your Email Address'
});
}
router.get('/signup', ifUserRedirect, getEmailSignin);
router.get('/signin', ifUserRedirect, getEmailSignin);
router.get('/email-signin', ifUserRedirect, getEmailSignin);
router.get('/signout', (req, res) => {
req.logout();
res.redirect('/');
});
router.get(
'/deprecated-signin',
ifUserRedirect,
(req, res) => res.render('account/deprecated-signin', {
title: 'Sign in to freeCodeCamp using a Deprecated Login'
})
);
const defaultErrorMsg = dedent`
Oops, something is not right,
please request a fresh link to sign in / sign up.
`;
const passwordlessGetValidators = [
check('email')
.isBase64()
.withMessage('Email should be a base64 encoded string.'),
check('token')
.exists()
.withMessage('Token should exist.')
// based on strongloop/loopback/common/models/access-token.js#L15
.isLength({ min: 64, max: 64 })
.withMessage('Token is not the right length.')
];
function getPasswordlessAuth(req, res, next) {
const {
query: {
email: encodedEmail,
token: authTokenId
} = {}
} = req;
const validation = validationResult(req)
.formatWith(createValidatorErrorFormatter('errors', '/email-signup'));
if (!validation.isEmpty()) {
const errors = validation.array();
return next(errors.pop());
}
const email = User.decodeEmail(encodedEmail);
if (!isEmail(email)) {
return next(wrapHandledError(
new TypeError('decoded email is invalid'),
{
type: 'info',
message: 'The email encoded in the link is incorrectly formatted',
redirectTo: '/email-sign'
}
));
}
// first find
return AuthToken.findOne$({ where: { id: authTokenId } })
.flatMap(authToken => {
if (!authToken) {
throw wrapHandledError(
new Error(`no token found for id: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/email-signin'
}
);
}
// find user then validate and destroy email validation token
// finally retun user instance
return User.findOne$({ where: { id: authToken.userId } })
.flatMap(user => {
if (!user) {
throw wrapHandledError(
new Error(`no user found for token: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/email-signin'
}
);
}
if (user.email !== email) {
throw wrapHandledError(
new Error('user email does not match'),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: '/email-signin'
}
);
}
return authToken.validate()
.map(isValid => {
if (!isValid) {
throw wrapHandledError(
new Error('token is invalid'),
{
type: 'info',
message: `
Looks like the link you clicked has expired,
please request a fresh link, to sign in.
`,
redirectTo: '/email-signin'
}
);
}
return authToken.destroy();
})
.map(() => user);
});
})
// at this point token has been validated and destroyed
// update user and log them in
.map(user => user.loginByRequest(req, res))
.do(() => {
let redirectTo = '/';
if (
req.session &&
req.session.returnTo
) {
redirectTo = req.session.returnTo;
}
req.flash('success', { msg:
'Success! You have signed in to your account. Happy Coding!'
});
return res.redirect(redirectTo);
})
.subscribe(
() => {},
next
);
}
router.get(
'/passwordless-auth',
ifUserRedirect,
passwordlessGetValidators,
getPasswordlessAuth
);
const passwordlessPostValidators = [
check('email')
.isEmail()
.withMessage('Email is not a valid email address.')
];
function postPasswordlessAuth(req, res, next) {
const { body: { email } = {} } = req;
const validation = validationResult(req)
.formatWith(createValidatorErrorFormatter('errors', '/email-signup'));
if (!validation.isEmpty()) {
const errors = validation.array();
return next(errors.pop());
}
return User.findOne$({ where: { email } })
.flatMap(_user => Observable.if(
// if no user found create new user and save to db
_.constant(_user),
Observable.of(_user),
User.create$({ email })
)
.flatMap(user => user.requestAuthEmail(!_user))
)
.do(msg => res.status(200).send({ message: msg }))
.subscribe(_.noop, next);
}
api.post(
'/passwordless-auth',
ifUserRedirect,
passwordlessPostValidators,
postPasswordlessAuth
);
app.use('/:lang', router);
app.use(api);
}; };

View File

@@ -14,9 +14,14 @@ import {
import { observeQuery } from '../utils/rx'; import { observeQuery } from '../utils/rx';
import { import {
respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
frontEndChallengeId, frontEndChallengeId,
dataVisChallengeId, dataVisId,
backEndChallengeId apisMicroservicesId,
backEndChallengeId,
infosecQaId
} from '../utils/constantStrings.json'; } from '../utils/constantStrings.json';
import { import {
@@ -60,9 +65,12 @@ function getIdsForCert$(id, Challenge) {
// { // {
// email: String, // email: String,
// username: String, // username: String,
// isFrontEndCert: Boolean, // isRespWebDesignCert: Boolean,
// isBackEndCert: Boolean, // isFrontEndLibsCert: Boolean,
// isDataVisCert: Boolean // isJsAlgoDataStructCert: Boolean,
// isDataVisCert: Boolean,
// isApisMicroservicesCert: Boolean,
// isInfosecQaCert: Boolean
// }, // },
// send$: Observable // send$: Observable
// ) => Observable // ) => Observable
@@ -71,17 +79,23 @@ function sendCertifiedEmail(
email, email,
name, name,
username, username,
isFrontEndCert, isRespWebDesignCert,
isBackEndCert, isFrontEndLibsCert,
isDataVisCert isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
}, },
send$ send$
) { ) {
if ( if (
!isEmail(email) || !isEmail(email) ||
!isFrontEndCert || !isRespWebDesignCert ||
!isBackEndCert || !isFrontEndLibsCert ||
!isDataVisCert !isJsAlgoDataStructCert ||
!isDataVisCert ||
!isApisMicroservicesCert ||
!isInfosecQaCert
) { ) {
return Observable.just(false); return Observable.just(false);
} }
@@ -107,8 +121,16 @@ export default function certificate(app) {
const certTypeIds = { const certTypeIds = {
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge), [certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisChallengeId, Challenge), [certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge) [certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
[certTypes.apisMicroservices]: getIdsForCert$(
apisMicroservicesId,
Challenge
),
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge)
}; };
router.post( router.post(
@@ -123,12 +145,42 @@ export default function certificate(app) {
verifyCert.bind(null, certTypes.backEnd) verifyCert.bind(null, certTypes.backEnd)
); );
router.post(
'/certificate/verify/responsive-web-design',
ifNoUser401,
verifyCert.bind(null, certTypes.respWebDesign)
);
router.post(
'/certificate/verify/front-end-libraries',
ifNoUser401,
verifyCert.bind(null, certTypes.frontEndLibs)
);
router.post(
'/certificate/verify/javascript-algorithms-data-structures',
ifNoUser401,
verifyCert.bind(null, certTypes.jsAlgoDataStruct)
);
router.post( router.post(
'/certificate/verify/data-visualization', '/certificate/verify/data-visualization',
ifNoUser401, ifNoUser401,
verifyCert.bind(null, certTypes.dataVis) verifyCert.bind(null, certTypes.dataVis)
); );
router.post(
'/certificate/verify/apis-microservices',
ifNoUser401,
verifyCert.bind(null, certTypes.apisMicroservices)
);
router.post(
'/certificate/verify/information-security-quality-assurance',
ifNoUser401,
verifyCert.bind(null, certTypes.infosecQa)
);
router.post( router.post(
'/certificate/honest', '/certificate/honest',
sendMessageToNonUser, sendMessageToNonUser,

View File

@@ -132,7 +132,7 @@ export default function commit(app) {
const { const {
nonprofit: nonprofitName = 'girl develop it', nonprofit: nonprofitName = 'girl develop it',
amount = '5', amount = '5',
goal = commitGoals.frontEndCert goal = commitGoals.respWebDesignCert
} = req.query; } = req.query;
const nonprofit = findNonprofit(nonprofitName); const nonprofit = findNonprofit(nonprofitName);

View File

@@ -1,15 +1,19 @@
const createDebugger = require('debug');
const log = createDebugger('fcc:boot:explorer');
module.exports = function mountLoopBackExplorer(app) { module.exports = function mountLoopBackExplorer(app) {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
return; return;
} }
var explorer; let explorer;
try { try {
explorer = require('loopback-component-explorer'); explorer = require('loopback-component-explorer');
} catch (err) { } catch (err) {
// Print the message only when the app was started via `app.listen()`. // Print the message only when the app was started via `app.listen()`.
// Do not print any message when the project is used as a component. // Do not print any message when the project is used as a component.
app.once('started', function() { app.once('started', function() {
console.log( log(
'Run `npm install loopback-component-explorer` to enable ' + 'Run `npm install loopback-component-explorer` to enable ' +
'the LoopBack explorer' 'the LoopBack explorer'
); );
@@ -17,13 +21,13 @@ module.exports = function mountLoopBackExplorer(app) {
return; return;
} }
var restApiRoot = app.get('restApiRoot'); const restApiRoot = app.get('restApiRoot');
var mountPath = '/explorer'; const mountPath = '/explorer';
explorer(app, { basePath: restApiRoot, mountPath }); explorer(app, { basePath: restApiRoot, mountPath });
app.once('started', function() { app.once('started', function() {
var baseUrl = app.get('url').replace(/\/$/, ''); const baseUrl = app.get('url').replace(/\/$/, '');
console.log('Browse your REST API at %s%s', baseUrl, mountPath); log('Browse your REST API at %s%s', baseUrl, mountPath);
}); });
}; };

View File

@@ -1,35 +0,0 @@
var path = require('path');
var loopback = require('loopback');
var express = require('express');
var port = 1337;
// this will listen to traffic on port 1337
// The purpose is to redirect any user who is direct to https
// instead of http by mistake. Our nginx proxy server will listen
// for https traffic and serve from this port on this server.
// the view being send will have a short timeout and a redirect
module.exports = function(loopbackApp) {
var app = express();
app.set('view engine', 'jade');
// views in ../views'
app.set('views', path.join(__dirname, '..'));
// server static files
app.use(loopback.static(path.join(
__dirname,
'../',
'../public'
)));
// all traffic will be redirected on page load;
app.use(function(req, res) {
return res.render('views/redirect-https');
});
loopbackApp.once('started', function() {
app.listen(port, function() {
console.log('https redirect listening on port %s', port);
});
});
};

View File

@@ -6,8 +6,13 @@ import emoji from 'node-emoji';
import { import {
frontEndChallengeId, frontEndChallengeId,
dataVisChallengeId, backEndChallengeId,
backEndChallengeId respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
dataVisId,
apisMicroservicesId,
infosecQaId
} from '../utils/constantStrings.json'; } from '../utils/constantStrings.json';
import certTypes from '../utils/certTypes.json'; import certTypes from '../utils/certTypes.json';
import { import {
@@ -24,27 +29,44 @@ import {
import supportedLanguages from '../../common/utils/supported-languages'; import supportedLanguages from '../../common/utils/supported-languages';
import { getChallengeInfo, cachedMap } from '../utils/map'; import { getChallengeInfo, cachedMap } from '../utils/map';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
const debug = debugFactory('fcc:boot:user'); const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map'); const sendNonUserToMap = ifNoUserRedirectTo('/map');
const certIds = { const certIds = {
[certTypes.frontEnd]: frontEndChallengeId, [certTypes.frontEnd]: frontEndChallengeId,
[certTypes.dataVis]: dataVisChallengeId, [certTypes.backEnd]: backEndChallengeId,
[certTypes.backEnd]: backEndChallengeId [certTypes.respWebDesign]: respWebDesignId,
[certTypes.frontEndLibs]: frontEndLibsId,
[certTypes.jsAlgoDataStruct]: jsAlgoDataStructId,
[certTypes.dataVis]: dataVisId,
[certTypes.apisMicroservices]: apisMicroservicesId,
[certTypes.infosecQa]: infosecQaId
}; };
const certViews = { const certViews = {
[certTypes.frontEnd]: 'certificate/front-end.jade', [certTypes.frontEnd]: 'certificate/front-end.jade',
[certTypes.dataVis]: 'certificate/data-vis.jade',
[certTypes.backEnd]: 'certificate/back-end.jade', [certTypes.backEnd]: 'certificate/back-end.jade',
[certTypes.fullStack]: 'certificate/full-stack.jade' [certTypes.fullStack]: 'certificate/full-stack.jade',
[certTypes.respWebDesign]: 'certificate/responsive-web-design.jade',
[certTypes.frontEndLibs]: 'certificate/front-end-libraries.jade',
[certTypes.jsAlgoDataStruct]:
'certificate/javascript-algorithms-and-data-structures.jade',
[certTypes.dataVis]: 'certificate/data-visualization.jade',
[certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
[certTypes.infosecQa]:
'certificate/information-security-and-quality-assurance.jade'
}; };
const certText = { const certText = {
[certTypes.frontEnd]: 'Front End certified', [certTypes.frontEnd]: 'Front End certified',
[certTypes.dataVis]: 'Data Vis Certified',
[certTypes.backEnd]: 'Back End Certified', [certTypes.backEnd]: 'Back End Certified',
[certTypes.fullStack]: 'Full Stack Certified' [certTypes.fullStack]: 'Full Stack Certified',
[certTypes.respWebDesign]: 'Responsive Web Design Certified',
[certTypes.frontEndLibs]: 'Front End Libraries Certified',
[certTypes.jsAlgoDataStruct]:
'JavaScript Algorithms and Data Structures Certified',
[certTypes.dataVis]: 'Data Visualization Certified',
[certTypes.apisMicroservices]: 'APIs and Microservices Certified',
[certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
}; };
const dateFormat = 'MMM DD, YYYY'; const dateFormat = 'MMM DD, YYYY';
@@ -139,7 +161,7 @@ function buildDisplayChallenges(
module.exports = function(app) { module.exports = function(app) {
const router = app.loopback.Router(); const router = app.loopback.Router();
const api = app.loopback.Router(); const api = app.loopback.Router();
const { AccessToken, Email, User } = app.models; const { Email, User } = app.models;
const map$ = cachedMap(app.models); const map$ = cachedMap(app.models);
function findUserByUsername$(username, fields) { function findUserByUsername$(username, fields) {
@@ -153,23 +175,6 @@ module.exports = function(app) {
); );
} }
AccessToken.findOne$ = Observable.fromNodeCallback(
AccessToken.findOne, AccessToken
);
router.get('/login', function(req, res) {
res.redirect(301, '/signin');
});
router.get('/logout', function(req, res) {
res.redirect(301, '/signout');
});
router.get('/signup', getEmailSignin);
router.get('/signin', getEmailSignin);
router.get('/signout', signout);
router.get('/email-signin', getEmailSignin);
router.get('/deprecated-signin', getDepSignin);
router.get('/passwordless-auth', invalidateAuthToken, getPasswordlessAuth);
api.post('/passwordless-auth', postPasswordlessAuth);
router.get( router.get(
'/delete-my-account', '/delete-my-account',
sendNonUserToMap, sendNonUserToMap,
@@ -208,11 +213,6 @@ module.exports = function(app) {
showCert.bind(null, certTypes.frontEnd) showCert.bind(null, certTypes.frontEnd)
); );
api.get(
'/:username/data-visualization-certification',
showCert.bind(null, certTypes.dataVis)
);
api.get( api.get(
'/:username/back-end-certification', '/:username/back-end-certification',
showCert.bind(null, certTypes.backEnd) showCert.bind(null, certTypes.backEnd)
@@ -223,6 +223,36 @@ module.exports = function(app) {
(req, res) => res.redirect(req.url.replace('full-stack', 'back-end')) (req, res) => res.redirect(req.url.replace('full-stack', 'back-end'))
); );
api.get(
'/:username/responsive-web-design-certification',
showCert.bind(null, certTypes.respWebDesign)
);
api.get(
'/:username/front-end-libraries-certification',
showCert.bind(null, certTypes.frontEndLibs)
);
api.get(
'/:username/javascript-algorithms-data-structures-certification',
showCert.bind(null, certTypes.jsAlgoDataStruct)
);
api.get(
'/:username/data-visualization-certification',
showCert.bind(null, certTypes.dataVis)
);
api.get(
'/:username/apis-microservices-certification',
showCert.bind(null, certTypes.apisMicroservices)
);
api.get(
'/:username/information-security-quality-assurance-certification',
showCert.bind(null, certTypes.infosecQa)
);
router.get('/:username', showUserProfile); router.get('/:username', showUserProfile);
router.get( router.get(
'/:username/report-user/', '/:username/report-user/',
@@ -240,179 +270,6 @@ module.exports = function(app) {
app.use('/:lang', router); app.use('/:lang', router);
app.use(api); app.use(api);
const defaultErrorMsg = [ 'Oops, something is not right, please request a ',
'fresh link to sign in / sign up.' ].join('');
function postPasswordlessAuth(req, res) {
if (req.user || !(req.body && req.body.email)) {
return res.redirect('/');
}
return User.requestAuthEmail(req.body.email)
.then(msg => {
return res.status(200).send({ message: msg });
})
.catch(err => {
debug(err);
return res.status(200).send({ message: defaultErrorMsg });
});
}
function invalidateAuthToken(req, res, next) {
if (req.user) {
res.redirect('/');
}
if (!req.query || !req.query.email || !req.query.token) {
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const authTokenId = req.query.token;
const authEmailId = new Buffer(req.query.email, 'base64').toString();
return AccessToken.findOne$({ where: {id: authTokenId} })
.map(authToken => {
if (!authToken) {
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const userId = authToken.userId;
return User.findById(userId, (err, user) => {
if (err || !user || user.email !== authEmailId) {
debug(err);
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
return authToken.validate((err, isValid) => {
if (err) { throw err; }
if (!isValid) {
req.flash('info', { msg: [ 'Looks like the link you clicked has',
'expired, please request a fresh link, to sign in.'].join('')
});
return res.redirect('/email-signin');
}
return authToken.destroy((err) => {
if (err) { debug(err); }
next();
});
});
});
})
.subscribe(
() => {},
next
);
}
function getPasswordlessAuth(req, res, next) {
if (req.user) {
req.flash('info', {
msg: 'Hey, looks like youre already signed in.'
});
return res.redirect('/');
}
if (!req.query || !req.query.email || !req.query.token) {
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const email = new Buffer(req.query.email, 'base64').toString();
return User.findOne$({ where: { email }})
.map(user => {
if (!user) {
debug(`did not find a valid user with email: ${email}`);
req.flash('info', { msg: defaultErrorMsg });
return res.redirect('/email-signin');
}
const emailVerified = true;
const emailAuthLinkTTL = null;
const emailVerifyTTL = null;
user.update$({
emailVerified, emailAuthLinkTTL, emailVerifyTTL
})
.do((user) => {
user.emailVerified = emailVerified;
user.emailAuthLinkTTL = emailAuthLinkTTL;
user.emailVerifyTTL = emailVerifyTTL;
});
return user.createAccessToken(
{ ttl: User.settings.ttl }, (err, accessToken) => {
if (err) { throw err; }
var config = {
signed: !!req.signedCookies,
maxAge: accessToken.ttl
};
if (accessToken && accessToken.id) {
debug('setting cookies');
res.cookie('access_token', accessToken.id, config);
res.cookie('userId', accessToken.userId, config);
}
return req.logIn({
id: accessToken.userId.toString() }, err => {
if (err) { return next(err); }
debug('user logged in');
if (req.session && req.session.returnTo) {
var redirectTo = req.session.returnTo;
if (redirectTo === '/map-aside') {
redirectTo = '/map';
}
return res.redirect(redirectTo);
}
req.flash('success', { msg:
'Success! You have signed in to your account. Happy Coding!'
});
return res.redirect('/');
});
});
})
.subscribe(
() => {},
next
);
}
function signout(req, res) {
req.logout();
res.redirect('/');
}
function getDepSignin(req, res) {
if (req.user) {
return res.redirect('/');
}
return res.render('account/deprecated-signin', {
title: 'Sign in to freeCodeCamp using a Deprecated Login'
});
}
function getEmailSignin(req, res) {
if (req.user) {
return res.redirect('/');
}
if (isSignUpDisabled) {
return res.render('account/beta', {
title: 'New sign ups are disabled'
});
}
return res.render('account/email-signin', {
title: 'Sign in to freeCodeCamp using your Email Address'
});
}
function getAccount(req, res) { function getAccount(req, res) {
const { username } = req.user; const { username } = req.user;
return res.redirect('/' + username); return res.redirect('/' + username);
@@ -586,9 +443,14 @@ module.exports = function(app) {
isLocked: true, isLocked: true,
isAvailableForHire: true, isAvailableForHire: true,
isFrontEndCert: true, isFrontEndCert: true,
isDataVisCert: true,
isBackEndCert: true, isBackEndCert: true,
isFullStackCert: true, isFullStackCert: true,
isRespWebDesignCert: true,
isFrontEndLibsCert: true,
isJsAlgoDataStructCert: true,
isDataVisCert: true,
isApisMicroservicesCert: true,
isInfosecQaCert: true,
isHonest: true, isHonest: true,
username: true, username: true,
name: true, name: true,

View File

@@ -7,8 +7,7 @@
"rest": { "rest": {
"handleErrors": false, "handleErrors": false,
"normalizeHttpPath": false, "normalizeHttpPath": false,
"xml": false, "xml": false
"handleErrors": false
}, },
"json": { "json": {
"strict": false, "strict": false,

View File

@@ -56,7 +56,8 @@
"./middlewares/jade-helpers": {}, "./middlewares/jade-helpers": {},
"./middlewares/migrate-completed-challenges": {}, "./middlewares/migrate-completed-challenges": {},
"./middlewares/add-lang": {}, "./middlewares/add-lang": {},
"./middlewares/flash-cheaters": {} "./middlewares/flash-cheaters": {},
"./middlewares/passport-login": {}
}, },
"files": {}, "files": {},
"final:after": { "final:after": {
@@ -64,9 +65,9 @@
"./middlewares/error-handlers": {}, "./middlewares/error-handlers": {},
"strong-error-handler": { "strong-error-handler": {
"params": { "params": {
"debug": false, "debug": false,
"log": true "log": true
} }
} }
} }
} }

View File

@@ -1,33 +1,83 @@
import errorHandler from 'errorhandler'; import { inspect } from 'util';
import _ from 'lodash/fp';
import accepts from 'accepts'; import accepts from 'accepts';
import { unwrapHandledError } from '../utils/create-handled-error.js'; import { unwrapHandledError } from '../utils/create-handled-error.js';
export default function prodErrorHandler() { const isDev = process.env.NODE_ENV !== 'production';
if (process.env.NODE_ENV === 'development') {
return errorHandler({ log: true }); const toString = Object.prototype.toString;
// is full error or just trace
// _.toString(new Error('foo')) => "Error: foo
// Object.prototype.toString.call(new Error('foo')) => "[object Error]"
const isInspect = val => !val.stack && _.toString(val) === toString.call(val);
const stringifyErr = val => {
if (val.stack) {
return String(val.stack);
} }
const str = String(val);
return isInspect(val) ?
inspect(val) :
str;
};
const createStackHtml = _.flow(
_.cond([
[isInspect, err => [err]],
// may be stack or just err.msg
[_.stubTrue, _.flow(stringifyErr, _.split('\n'), _.tail) ]
]),
_.map(_.escape),
_.map(line => `<li>${line}</lin>`),
_.join('')
);
const createErrorTitle = _.cond([
[
_.negate(isInspect),
_.flow(stringifyErr, _.split('\n'), _.head, _.defaultTo('Error'))
],
[_.stubTrue, _.constant('Error')]
]);
export default function prodErrorHandler() {
// error handling in production. // error handling in production.
// disabling eslint due to express parity rules for error handlers // disabling eslint due to express parity rules for error handlers
return function(err, req, res, next) { // eslint-disable-line return function(err, req, res, next) { // eslint-disable-line
// respect err.status const handled = unwrapHandledError(err);
if (err.status) { // respect handled error status
res.statusCode = err.status; let status = handled.status || err.status || res.statusCode;
} if (!handled.status && status < 400) {
status = 500;
// default status code to 500
if (res.statusCode < 400) {
res.statusCode = 500;
} }
res.status(status);
// parse res type // parse res type
const accept = accepts(req); const accept = accepts(req);
const type = accept.type('html', 'json', 'text'); const type = accept.type('html', 'json', 'text');
const handled = unwrapHandledError(err);
const redirectTo = handled.redirectTo || '/map'; const redirectTo = handled.redirectTo || '/';
const message = handled.message || const message = handled.message ||
'Oops! Something went wrong. Please try again later'; 'Oops! Something went wrong. Please try again later';
if (isDev) {
console.error(err);
}
if (type === 'html') { if (type === 'html') {
if (isDev) {
return res.render(
'dev-error',
{
...handled,
stack: createStackHtml(err),
errorTitle: createErrorTitle(err),
title: 'freeCodeCamp - Server Error',
status
}
);
}
if (typeof req.flash === 'function') { if (typeof req.flash === 'function') {
req.flash( req.flash(
handled.type || 'danger', handled.type || 'danger',
@@ -39,6 +89,7 @@ export default function prodErrorHandler() {
} else if (type === 'json') { } else if (type === 'json') {
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
return res.send({ return res.send({
type: handled.type || 'errors',
message message
}); });
// plain text // plain text

View File

@@ -8,7 +8,7 @@ import {
const log = debug('fcc:middlewares:error-reporter'); const log = debug('fcc:middlewares:error-reporter');
export default function keymetrics() { export default function errorHandler() {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
return (err, req, res, next) => { return (err, req, res, next) => {
if (isHandledError(err)) { if (isHandledError(err)) {

View File

@@ -0,0 +1,21 @@
import _ from 'lodash';
import { Observable } from 'rx';
import { login } from 'passport/lib/http/request';
// make login polymorphic
// if supplied callback it works as normal
// if called without callback it returns an observable
// login(user, options?, cb?) => Void|Observable
function login$(...args) {
console.log('args');
if (_.isFunction(_.last(args))) {
return login.apply(this, args);
}
return Observable.fromNodeCallback(login).apply(this, args);
}
export default function passportLogin() {
return (req, res, next) => {
req.login = req.logIn = login$;
next();
};
}

View File

@@ -30,25 +30,27 @@ export default function() {
customSanitizers: { customSanitizers: {
// Refer : http://stackoverflow.com/a/430240/1932901 // Refer : http://stackoverflow.com/a/430240/1932901
trimTags(value) { trimTags(value) {
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*'; const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
const tagOrComment = new RegExp( const tagOrComment = new RegExp(
'<(?:' '<(?:'
// Comment body. // Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)' + '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided. // Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*' + '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*' + '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name // Regular name
+ '|/?[a-z]' + '|/?[a-z]'
+ tagBody + tagBody
+ ')>', + ')>',
'gi'); 'gi'
let rawValue; );
do { let rawValue;
rawValue = value; do {
value = value.replace(tagOrComment, ''); rawValue = value;
} while (value !== rawValue); value = value.replace(tagOrComment, '');
return value.replace(/</g, '&lt;'); } while (value !== rawValue);
return value.replace(/</g, '&lt;');
} }
} }
}); });

View File

@@ -78,5 +78,9 @@
"about": { "about": {
"dataSource": "db", "dataSource": "db",
"public": true "public": true
},
"AuthToken": {
"dataSource": "db",
"public": false
} }
} }

View File

@@ -0,0 +1,15 @@
import { Observable } from 'rx';
export default function(AuthToken) {
AuthToken.on('dataSourceAttached', () => {
AuthToken.findOne$ = Observable.fromNodeCallback(
AuthToken.findOne.bind(AuthToken)
);
AuthToken.prototype.validate = Observable.fromNodeCallback(
AuthToken.prototype.validate
);
AuthToken.prototype.destroy = Observable.fromNodeCallback(
AuthToken.prototype.destroy
);
});
}

View File

@@ -0,0 +1,13 @@
{
"name": "AuthToken",
"base": "AccessToken",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

View File

@@ -1,32 +1,33 @@
// this ensures node understands the future // this ensures node understands the future
require('babel-register'); require('babel-register');
const _ = require('lodash');
const createDebugger = require('debug');
var startTime = Date.now(); const log = createDebugger('fcc:server:production-start');
var timeoutHandler; const startTime = Date.now();
// force logger to always output
// this may be brittle
log.enabled = true;
// this is where server starts booting up // this is where server starts booting up
var app = require('./server'); const app = require('./server');
console.log('waiting for db to connect');
var onConnect = function() { let timeoutHandler;
console.log('db connected in %s ms', Date.now() - startTime); let killTime = 15;
const onConnect = _.once(() => {
log('db connected in: %s', Date.now() - startTime);
if (timeoutHandler) { if (timeoutHandler) {
clearTimeout(timeoutHandler); clearTimeout(timeoutHandler);
} }
app.start(); app.start();
}; });
timeoutHandler = setTimeout(function() { timeoutHandler = setTimeout(() => {
var message = const message = `db did not connect after ${killTime}s -- crashing hard`;
'db did not connect after ' +
(Date.now() - startTime) +
' ms --- crashing hard';
console.log(message);
// purposely shutdown server // purposely shutdown server
// pm2 should restart this in production // pm2 should restart this in production
throw new Error(message); throw new Error(message);
}, 15000); }, killTime * 1000);
app.dataSources.db.on('connected', onConnect); app.dataSources.db.on('connected', onConnect);

View File

@@ -1,4 +1,5 @@
require('dotenv').load(); require('dotenv').load();
require('./utils/webpack-code-split-polyfill');
if (process.env.OPBEAT_ID) { if (process.env.OPBEAT_ID) {
console.log('loading opbeat'); console.log('loading opbeat');
@@ -9,33 +10,23 @@ if (process.env.OPBEAT_ID) {
}); });
} }
var _ = require('lodash'), const _ = require('lodash');
Rx = require('rx'), const Rx = require('rx');
loopback = require('loopback'), const loopback = require('loopback');
boot = require('loopback-boot'), const boot = require('loopback-boot');
expressState = require('express-state'), const expressState = require('express-state');
path = require('path'), const path = require('path');
setupPassport = require('./component-passport'); const setupPassport = require('./component-passport');
const createDebugger = require('debug');
const log = createDebugger('fcc:server');
// force logger to always output
// this may be brittle
log.enabled = true;
// polyfill for webpack bundle splitting
const requireProto = Object.getPrototypeOf(require);
if (!requireProto.hasOwnProperty('ensure')) {
Object.defineProperties(
requireProto,
{
ensure: {
value: function ensure(modules, callback) {
callback(this);
},
writable: false,
enumerable: false
}
}
);
}
Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production'; Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production';
var app = loopback(); const app = loopback();
var isBeta = !!process.env.BETA; const isBeta = !!process.env.BETA;
expressState.extend(app); expressState.extend(app);
app.set('state namespace', '__fcc__'); app.set('state namespace', '__fcc__');
@@ -52,17 +43,36 @@ boot(app, {
setupPassport(app); setupPassport(app);
const { db } = app.datasources;
db.on('connected', _.once(() => log('db connected')));
app.start = _.once(function() { app.start = _.once(function() {
app.listen(app.get('port'), function() { const server = app.listen(app.get('port'), function() {
app.emit('started'); app.emit('started');
console.log( log(
'freeCodeCamp server listening on port %d in %s', 'freeCodeCamp server listening on port %d in %s',
app.get('port'), app.get('port'),
app.get('env') app.get('env')
); );
if (isBeta) { if (isBeta) {
console.log('freeCodeCamp is in beta mode'); log('freeCodeCamp is in beta mode');
} }
log(`connecting to db at ${db.settings.url}`);
});
process.on('SIGINT', () => {
log('Shutting down server');
server.close(() => {
log('Server is closed');
});
log('closing db connection');
db.disconnect()
.then(() => {
log('DB connection closed');
// exit process
// this may close kept alive sockets
// eslint-disable-next-line no-process-exit
process.exit(0);
});
}); });
}); });

View File

@@ -21,6 +21,11 @@ const publicUserProps = [
'isBackEndCert', 'isBackEndCert',
'isDataVisCert', 'isDataVisCert',
'isFullStackCert', 'isFullStackCert',
'isRespWebDesignCert',
'isFrontEndLibsCert',
'isJsAlgoDataStructCert',
'isApisMicroservicesCert',
'isInfosecQaCert',
'githubURL', 'githubURL',
'sendMonthlyEmail', 'sendMonthlyEmail',

View File

@@ -1,6 +1,11 @@
{ {
"frontEnd": "isFrontEndCert", "frontEnd": "isFrontEndCert",
"backEnd": "isBackEndCert", "backEnd": "isBackEndCert",
"fullStack": "isFullStackCert",
"respWebDesign": "isRespWebDesignCert",
"frontEndLibs": "isFrontEndLibsCert",
"jsAlgoDataStruct": "isJsAlgoDataStructCert",
"dataVis": "isDataVisCert", "dataVis": "isDataVisCert",
"fullStack": "isFullStackCert" "apisMicroservices": "isApisMicroservicesCert",
"infosecQa": "isInfosecQaCert"
} }

View File

@@ -1,6 +1,11 @@
{ {
"frontEndCert": "Front End Development Certification", "frontEndCert": "Front End Development Certification",
"backEndCert": "Back End Development Certification", "backEndCert": "Back End Development Certification",
"dataVisCert": "Data Visualisation Certification", "fullStackCert": "Full Stack Development Certification",
"fullStackCert": "Full Stack Development Certification" "respWebDesign": "Responsive Web Design Certification",
"frontEndLibs": "Front End Libraries Certification",
"jsAlgoDataStruct": "JavaScript Algorithms and Data Structures Certification",
"dataVis": "Data Visualisation Certification",
"apisMicroservices": "APIs and Microservices Certification",
"infosecQa": "Information Security and Quality Assurance Certification"
} }

View File

@@ -10,9 +10,14 @@ export { commitGoals };
export function completeCommitment$(user) { export function completeCommitment$(user) {
const { const {
isFrontEndCert, isFrontEndCert,
isDataVisCert,
isBackEndCert, isBackEndCert,
isFullStackCert isFullStackCert,
isRespWebDesignCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
} = user; } = user;
return Observable.fromNodeCallback(user.pledge, user)() return Observable.fromNodeCallback(user.pledge, user)()
@@ -25,9 +30,15 @@ export function completeCommitment$(user) {
if ( if (
(isFrontEndCert && goal === commitGoals.frontEndCert) || (isFrontEndCert && goal === commitGoals.frontEndCert) ||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
(isBackEndCert && goal === commitGoals.backEndCert) || (isBackEndCert && goal === commitGoals.backEndCert) ||
(isFullStackCert && goal === commitGoals.fullStackCert) (isFullStackCert && goal === commitGoals.fullStackCert) ||
(isRespWebDesignCert && goal === commitGoals.respWebDesignCert) ||
(isFrontEndLibsCert && goal === commitGoals.frontEndLibsCert) ||
(isJsAlgoDataStructCert && goal === commitGoals.jsAlgoDataStructCert) ||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
(isApisMicroservicesCert &&
goal === commitGoals.apisMicroservicesCert) ||
(isInfosecQaCert && goal === commitGoals.infosecQaCert)
) { ) {
debug('marking goal complete'); debug('marking goal complete');
pledge.isCompleted = true; pledge.isCompleted = true;

View File

@@ -1,6 +1,11 @@
{ {
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36", "gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36",
"frontEndChallengeId": "561add10cb82ac38a17513be", "frontEndChallengeId": "561add10cb82ac38a17513be",
"dataVisChallengeId": "561add10cb82ac38a17513b3", "backEndChallengeId": "660add10cb82ac38a17513be",
"backEndChallengeId": "660add10cb82ac38a17513be" "respWebDesignId": "561add10cb82ac38a17513bc",
"frontEndLibsId": "561acd10cb82ac38a17513bc",
"jsAlgoDataStructId": "561abd10cb81ac38a17513bc",
"dataVisId": "561add10cb82ac39a17513bc",
"apisMicroservicesId": "561add10cb82ac38a17523bc",
"infosecQaId": "561add10cb82ac38a17213bc"
} }

View File

@@ -11,8 +11,20 @@ export function unwrapHandledError(err) {
export function wrapHandledError(err, { export function wrapHandledError(err, {
type, type,
message, message,
redirectTo redirectTo,
status = 200
}) { }) {
err[_handledError] = { type, message, redirectTo }; err[_handledError] = { type, message, redirectTo, status };
return err; return err;
} }
export const createValidatorErrorFormatter = (type, redirectTo, status) =>
({ msg }) => wrapHandledError(
new Error(msg),
{
type,
message: msg,
redirectTo,
status
}
);

View File

@@ -209,6 +209,7 @@ export function getChallenge(
) { ) {
return map return map
.flatMap(({ entities, result: { superBlocks } }) => { .flatMap(({ entities, result: { superBlocks } }) => {
const superBlock = entities.superBlock;
const block = entities.block[blockDashedName]; const block = entities.block[blockDashedName];
const challenge = entities.challenge[challengeDashedName]; const challenge = entities.challenge[challengeDashedName];
return Observable.if( return Observable.if(
@@ -226,6 +227,7 @@ export function getChallenge(
`/challenges/${block.dashedName}/${challenge.dashedName}` : `/challenges/${block.dashedName}/${challenge.dashedName}` :
false, false,
entities: { entities: {
superBlock,
challenge: { challenge: {
[challenge.dashedName]: mapChallengeToLang(challenge, lang) [challenge.dashedName]: mapChallengeToLang(challenge, lang)
} }

View File

@@ -43,3 +43,13 @@ export function ifNotVerifiedRedirectToSettings(req, res, next) {
} }
return next(); return next();
} }
export function ifUserRedirectTo(path = '/', status) {
status = status === 302 ? 302 : 301;
return (req, res, next) => {
if (req.user) {
return res.status(status).redirect(path);
}
return next();
};
}

View File

@@ -0,0 +1,18 @@
export default function codeSplitPolyfill() {
// polyfill for webpack bundle splitting
const requireProto = Object.getPrototypeOf(require);
if (!requireProto.hasOwnProperty('ensure')) {
Object.defineProperties(
requireProto,
{
ensure: {
value: function ensure(modules, callback) {
callback(this);
},
writable: false,
enumerable: false
}
}
);
}
}

View File

@@ -34,18 +34,17 @@ block content
a(href="/deprecated-signin") Or click here if you want to sign in with other options. a(href="/deprecated-signin") Or click here if you want to sign in with other options.
script. script.
$(document).ready(function() { $(document).ready(function() {
function disableMagicButton (isDisabled) {
function disableMagicButton (isDisabled) { if (isDisabled) {
if (isDisabled) { $('#magic-btn')
$('#magic-btn') .prop('disabled', true)
.html('<span class="fa fa-circle-o-notch fa-spin fa-fw"></span>') .html('<span style="color:#E0E0E0;"><i class="fa fa-circle-o-notch fa-spin fa-fw"></i>Ok - We will attempt sending to the email above.</span>');
.prop('disabled', true); } else {
} else { $('#magic-btn')
$('#magic-btn') .prop('disabled', true)
.html('<span class="fa.fa-envelope">Get a magic link to sign in.</span>') .html('<span style="color:#E0E0E0;">Did not get a link? Reload the page and resend again.</span>');
.prop('disabled', false); }
}
} }
$('form').submit(function(event){ $('form').submit(function(event){
@@ -54,34 +53,53 @@ block content
disableMagicButton(true); disableMagicButton(true);
var $form = $(event.target); var $form = $(event.target);
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : $form.attr('action'), url : $form.attr('action'),
data : $form.serialize(), data : $form.serialize(),
dataType : 'json', dataType : 'json',
encode : true, encode : true,
xhrFields : { withCredentials: true } xhrFields : { withCredentials: true }
}) })
.fail(error => { .fail(error => {
if (error.responseText){ if (error.responseText){
var data = JSON.parse(error.responseText); var data = JSON.parse(error.responseText);
if(data.error && data.error.message) if(data.error && data.error.message) {
$('#flash-content').html(data.error.message); $('#flash-content').html(data.error.message);
$('#flash-board') $('#flash-board')
.removeClass('alert-success') .removeClass('alert-success')
.addClass('alert-info') .addClass('alert-info')
.slideDown(400)
.delay(800)
.fadeIn(); .fadeIn();
disableMagicButton(false); disableMagicButton(false);
} }
}
}) })
.done(data =>{ .done(data => {
if(data && data.message){ if(data && data.message) {
var alertType = 'alert-';
switch (data.type) {
case 'errors': {
alertType += 'danger';
break
}
case 'success': {
alertType += 'success';
break
}
default: {
alertType += 'info';
}
}
$('#flash-content').html(data.message); $('#flash-content').html(data.message);
$('#flash-board') $('#flash-board')
.removeClass('alert-info') .removeClass('alert-info alert-success alert-danger')
.addClass('alert-success') .addClass(alertType)
.fadeIn(); .slideDown(400)
.delay(800)
.fadeIn();
disableMagicButton(false); disableMagicButton(false);
} }
}); });
}); });
}); });

View File

@@ -9,10 +9,10 @@ block content
if (!user.isGithubCool) if (!user.isGithubCool)
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github') a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github')
i.fa.fa-github i.fa.fa-github
| Link my GitHub to unlock my portfolio | Link my GitHub to enable my public profile
.col-xs-12 .col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings') a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings')
| Update your settings | Update my settings
.col-xs-12 .col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/signout') a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/signout')
| Sign me out of freeCodeCamp | Sign me out of freeCodeCamp
@@ -56,6 +56,21 @@ block content
if isBackEndCert if isBackEndCert
.button-spacer .button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/back-end-certification') View My Back End Development Certification a.btn.btn-primary.btn-block(href='/' + username + '/back-end-certification') View My Back End Development Certification
if isRespWebDesignCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/responsive-web-design-certification') View My Responsive Web Design Certification
if isFrontEndLibsCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/front-end-libraries-certification') View My Front End Libraries Certification
if isJsAlgoDataStructCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/javascript-algorithms-data-structures-certification') View My JavaScript Algorithms Data Structures Certification
if isApisMicroservicesCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/apis-microservices-certification') View My APIs Microservices Certification
if isInfosecQaCert
.button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/information-security-quality-assurance-certification') View My Information Sequrity Quality Assurance Certification
if (user && user.username != username) if (user && user.username != username)
.button-spacer .button-spacer
a.btn.btn-primary.btn-block(href='/' + username + '/report-user/') Report this user's profile for abuse a.btn.btn-primary.btn-block(href='/' + username + '/report-user/') Report this user's profile for abuse

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong Advanced Frontend Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/advanced-front-end-certification

View File

@@ -0,0 +1,32 @@
meta(name='viewport', content='width=device-width, initial-scale=1')
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
include styles
.certificate-wrapper.container
.row
header
.col-md-5.col-sm-12
.logo
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
.col-md-7.col-sm-12
.issue-date Issued&nbsp;
strong #{date}
section.information
.information-container
h3 This certifies that
h1
strong= name
h3 has successfully completed freeCodeCamp's
h1
strong APIs and Microservices Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
p
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/apis-and-microservices-certification

Some files were not shown because too many files have changed in this diff Show More