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"],
"plugins": [
"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/frame-runner*
public/css/main*
webpack-bundle-stats.html
server/rev-manifest.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
- [Prerequisites](#prerequisites)
- [Forking The Project](#forking-the-project)
- [Create A Branch](#create-a-branch)
- [Setup Linting](#setup-linting)
- [Setup freeCodeCamp](#setup-freecodecamp)
- [Forking the Project](#forking-the-project)
- [Create a Branch](#create-a-branch)
- [Set Up Linting](#set-up-linting)
- [Set Up MailHog](#set-up-mailhog)
- [Set Up freeCodeCamp](#set-up-freecodecamp)
- [Make Changes](#make-changes)
- [Run The Test Suite](#run-the-test-suite)
- [Squash Your Commits](#squash-your-commits)
- [Commit Message](#commit-message)
- [Creating A Pull Request](#creating-a-pull-request)
- [Creating a Pull Request](#creating-a-pull-request)
- [Common Steps](#common-steps)
- [How We Review and Merge Pull Requests](#how-we-review-and-merge-pull-requests)
- [How We Close Stale Issues](#how-we-close-stale-issues)
- [Next Steps](#next-steps)
- [Other resources](#other-resources)
- [Other Resources](#other-resources)
### 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` |
| [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` |
> _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
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 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
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.
3. Create a parent projects directory on your system. For this guide, it will be assumed that it is `/mean/`
#### Forking 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/))
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
@@ -83,26 +81,26 @@ Platform-specific guides to setting up a development environment:
$ 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
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
$ 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
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:
@@ -135,7 +133,7 @@ $ git push origin staging --force
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.
@@ -157,22 +155,73 @@ and to push to GitHub:
$ 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)).
> 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:
```bash
# Install NPM dependencies
npm install
```
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
# Create a copy of the "sample.env" and name it as ".env".
# Populate it with the necessary API keys and secrets:
# macOS / Linux
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.
@@ -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.
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:
```bash
@@ -212,14 +258,14 @@ mongod
# This command should only be run once.
npm run only-once
# start the application
# Start the application
npm run develop
```
Now navigate to your browser and open
<http://localhost:3000>. If the app loads,
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:
Now navigate to your browser and open <http://localhost:3000>. If the app loads, congratulations you're all set.
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.
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
# Remove all installed node modules
rm -rf node_modules
@@ -227,7 +273,7 @@ rm -rf node_modules
# Reinstall npm packages
npm install
# Seed the database (optional)
# Seed the database
node seed
# Re-start the application
@@ -235,9 +281,10 @@ npm run develop
```
### Make Changes
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 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.
#### Changes to the seed files
If you made changes to any file in the `/seed` directory, you need to run
```shell
$ node seed
@@ -254,6 +302,7 @@ $ node seed
in order to see the changes.
### Run The Test Suite
When you're ready to share your code, run the test suite:
```shell
@@ -262,34 +311,12 @@ $ npm test
and ensure all tests pass.
### Squash Your Commits
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
### Creating a Pull Request
#### What is a Pull Request?
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
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
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_):
- 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.
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.
b. Using Commitizen CLI:
- 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,
8. If you would want to add/remove changes to previous commit, 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).
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)
@@ -377,7 +400,7 @@ for further information
1. Once the edits have been committed, you will be prompted to create a pull
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.
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.
### 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).
@@ -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.
### 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:
@@ -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.
### Other resources
### Other Resources
- [Style Guide for freeCodeCamp
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
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)
- [Writing great git commit
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)

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2017, freeCodeCamp.
Copyright (c) 2018, freeCodeCamp.
All rights reserved.
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
-------
Copyright (c) 2017 freeCodeCamp.
Copyright (c) 2018 freeCodeCamp.
The content of this repository bound by the following LICENSE(S)
- The computer software is licensed under the [BSD-3-Clause](./LICENSE.md).

View File

@@ -34,4 +34,4 @@
.CodeMirror-lint-tooltip {
z-index: 9999;
}
}

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,11 @@ import FA from 'react-fontawesome';
import { Panel } from 'react-bootstrap';
import ns from './ns.json';
import Challenge from './Challenge.jsx';
import Challenges from './Challenges.jsx';
import {
toggleThisPanel,
makePanelOpenSelector,
makePanelHiddenSelector
makePanelOpenSelector
} from './redux';
import { makeBlockSelector } from '../entities';
@@ -21,15 +20,13 @@ function makeMapStateToProps(_, { dashedName }) {
return createSelector(
makeBlockSelector(dashedName),
makePanelOpenSelector(dashedName),
makePanelHiddenSelector(dashedName),
(block, isOpen, isHidden) => {
(block, isOpen) => {
return {
isOpen,
isHidden,
dashedName,
title: block.title,
time: block.time,
challenges: block.challenges
challenges: block.challenges || []
};
}
);
@@ -37,7 +34,6 @@ function makeMapStateToProps(_, { dashedName }) {
const propTypes = {
challenges: PropTypes.array,
dashedName: PropTypes.string,
isHidden: PropTypes.bool,
isOpen: PropTypes.bool,
time: 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() {
const {
title,
time,
dashedName,
isOpen,
isHidden,
challenges
} = this.props;
if (isHidden) {
return null;
}
return (
<Panel
bsClass={ `${ns}-accordion-panel` }
@@ -105,7 +85,7 @@ export class Block extends PureComponent {
key={ title }
onSelect={ this.handleSelect }
>
{ this.renderChallenges(challenges) }
{ isOpen && <Challenges challenges={ challenges } /> }
</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 debug from 'debug';
import {
clickOnChallenge,
makePanelHiddenSelector
} from './redux';
import { clickOnChallenge } from './redux';
import { userSelector } from '../redux';
import { challengeMapSelector } from '../entities';
import { Link } from '../Router';
@@ -23,7 +19,6 @@ const propTypes = {
isComingSoon: PropTypes.bool,
isCompleted: PropTypes.bool,
isDev: PropTypes.bool,
isHidden: PropTypes.bool,
isLocked: PropTypes.bool,
isRequired: PropTypes.bool,
title: PropTypes.string
@@ -34,11 +29,9 @@ function makeMapStateToProps(_, { dashedName }) {
return createSelector(
userSelector,
challengeMapSelector,
makePanelHiddenSelector(dashedName),
(
{ challengeMap: userChallengeMap },
challengeMap,
isHidden
challengeMap
) => {
const {
id,
@@ -51,7 +44,6 @@ function makeMapStateToProps(_, { dashedName }) {
const isCompleted = userChallengeMap ? !!userChallengeMap[id] : false;
return {
dashedName,
isHidden,
isCompleted,
title,
block,
@@ -115,12 +107,11 @@ export class Challenge extends PureComponent {
isComingSoon,
isCompleted,
isDev,
isHidden,
isLocked,
isRequired,
title
} = this.props;
if (isHidden || !title) {
if (!title) {
return null;
}
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 ns from './ns.json';
import Block from './Block.jsx';
import Blocks from './Blocks.jsx';
import {
toggleThisPanel,
makePanelOpenSelector,
makePanelHiddenSelector
makePanelOpenSelector
} from './redux';
import { makeSuperBlockSelector } from '../entities';
const dispatchActions = { toggleThisPanel };
const mapDispatchToProps = { toggleThisPanel };
// make selectors unique to each component
// see
// reactjs/reselect
// sharing-selectors-with-props-across-multiple-components
function makeMapStateToProps(_, { dashedName }) {
function mapStateToProps(_, { dashedName }) {
return createSelector(
makeSuperBlockSelector(dashedName),
makePanelOpenSelector(dashedName),
makePanelHiddenSelector(dashedName),
(superBlock, isOpen, isHidden) => ({
(superBlock, isOpen) => ({
isOpen,
isHidden,
dashedName,
title: superBlock.title || dashedName,
blocks: superBlock.blocks || [],
@@ -39,7 +36,6 @@ function makeMapStateToProps(_, { dashedName }) {
const propTypes = {
blocks: PropTypes.array,
dashedName: PropTypes.string,
isHidden: PropTypes.bool,
isOpen: PropTypes.bool,
message: PropTypes.string,
title: PropTypes.string,
@@ -56,18 +52,6 @@ export class SuperBlock extends PureComponent {
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) {
if (!message) {
return null;
@@ -98,12 +82,8 @@ export class SuperBlock extends PureComponent {
dashedName,
blocks,
message,
isOpen,
isHidden
isOpen
} = this.props;
if (isHidden) {
return null;
}
return (
<Panel
bsClass={ `${ns}-accordion-panel` }
@@ -116,9 +96,7 @@ export class SuperBlock extends PureComponent {
onSelect={ this.handleSelect }
>
{ this.renderMessage(message) }
<div className={ `${ns}-accordion-block` }>
{ this.renderBlocks(blocks) }
</div>
<Blocks blocks={ blocks } />
</Panel>
);
}
@@ -128,6 +106,6 @@ SuperBlock.displayName = 'SuperBlock';
SuperBlock.propTypes = propTypes;
export default connect(
makeMapStateToProps,
dispatchActions
mapStateToProps,
mapDispatchToProps
)(SuperBlock);

View File

@@ -98,7 +98,7 @@
}
.@{ns}-block-time {
color: #BBBBBB;
color: #555555;
@media (min-width: 721px) {
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{
// children: [...{
// name: (superBlock: String),
// isOpen: Boolean,
// isHidden: Boolean,
// children: [...{
// name: (blockName: String),
// isOpen: Boolean,
// isHidden: Boolean,
// children: [...{
// name: (challengeName: String),
// isHidden: Boolean
// }]
// }]
// }

View File

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

View File

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

View File

@@ -32,6 +32,9 @@
.navbar-brand {
padding-top: @navbar-logo-padding;
padding-bottom: @navbar-logo-padding;
display: flex;
align-items: center;
justify-content: center;
}
.nav-logo {
@@ -153,6 +156,28 @@ li.nav-avatar {
}
.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-nav {
& > li > a {
@@ -209,31 +234,31 @@ li.nav-avatar {
}
::-webkit-input-placeholder {
color: @brand-primary;
color: @input-color-placeholder;
}
::-moz-placeholder {
color: @brand-primary;
color: @input-color-placeholder;
}
::-ms-placeholder {
color: @brand-primary;
color: @input-color-placeholder;
}
::placeholder {
color: @brand-primary;
color: @input-color-placeholder;
}
.navbar-header {
flex-grow: 1;
display: flex;
align-items: center;
margin-right: 10px;
}
}
.logo-glyph {
height: 30px;
height: 28px;
width: auto;
}
@@ -258,4 +283,4 @@ li.nav-avatar {
display: block
}
}
}

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import _ from 'lodash';
import invariant from 'invariant';
import {
composeReducers,
createAction,
@@ -7,10 +6,11 @@ import {
handleActions
} from 'berkeleys-redux-utils';
import ns from '../ns.json';
import * as utils from './utils.js';
import windowEpic from './window-epic.js';
import dividerEpic from './divider-epic.js';
import ns from '../ns.json';
import { types as challengeTypes } from '../../routes/Challenges/redux';
export const epics = [
windowEpic,
@@ -31,8 +31,8 @@ export const types = createTypes([
'windowResized',
// commands
'updateNavHeight',
'hidePane'
'hidePane',
'updateNavHeight'
], ns);
export const panesMapUpdated = createAction(
@@ -51,13 +51,14 @@ export const mouseReleased = createAction(types.mouseReleased);
export const windowResized = createAction(types.windowResized);
// commands
export const updateNavHeight = createAction(types.updateNavHeight);
export const hidePane = createAction(types.hidePane);
export const updateNavHeight = createAction(types.updateNavHeight);
const defaultState = {
height: 600,
width: 800,
navHeight: 50,
isMapPaneHidden: false,
panes: [],
panesByName: {},
pressedDivider: null,
@@ -76,56 +77,14 @@ export const pressedDividerSelector =
export const widthSelector = state => getNS(state).width;
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 }) {
createPanesMap = normalizePanesMapCreator(createPanesMap);
createPanesMap = utils.normalizePanesMapCreator(createPanesMap);
function middleware({ getState }) {
return next => action => {
let finalAction = action;
const panesMap = panesMapSelector(getState());
if (isPanesAction(action, panesMap)) {
if (utils.isPanesAction(action, panesMap)) {
finalAction = {
...action,
meta: {
@@ -138,7 +97,7 @@ export default function createPanesAspects({ createPanesMap }) {
const result = next(finalAction);
const nextPanesMap = createPanesMap(getState(), action);
if (nextPanesMap) {
checkForTypeKeys(nextPanesMap);
utils.checkForTypeKeys(nextPanesMap);
next(panesMapUpdated(action.type, nextPanesMap));
}
return result;
@@ -154,17 +113,20 @@ export default function createPanesAspects({ createPanesMap }) {
pressedDivider: name
}),
[types.dividerMoved]: (state, { payload: clientX }) => {
const { width, pressedDivider: paneName } = state;
const {
panes,
panesByName,
pressedDivider: paneName,
width
} = state;
const dividerBuffer = (200 / width) * 100;
const paneIndex =
_.findIndex(state.panes, ({ name }) => paneName === name);
const currentPane = state.panesByName[paneName];
const rightPane =
state.panesByName[getPaneName(state.panes, paneIndex + 1)] || {};
const leftPane =
state.panesByName[getPaneName(state.panes, paneIndex - 1)] || {};
const rightBound = (rightPane.dividerLeft || 100) - dividerBuffer;
const leftBound = (leftPane.dividerLeft || 0) + dividerBuffer;
const currentPane = panesByName[paneName];
const rightPane = utils.getPane(panesByName, panes, paneIndex + 1);
const leftPane = utils.getPane(panesByName, panes, paneIndex - 1);
const rightBound = utils.getRightBound(rightPane, dividerBuffer);
const leftBound = utils.getLeftBound(leftPane, dividerBuffer);
const newPosition = _.clamp(
(clientX / width) * 100,
leftBound,
@@ -197,9 +159,13 @@ export default function createPanesAspects({ createPanesMap }) {
[types.updateNavHeight]: (state, { payload: navHeight }) => ({
...state,
navHeight
}),
[challengeTypes.toggleMap]: state => ({
...state,
isMapPaneHidden: !state.isMapPaneHidden
})
}),
defaultState,
defaultState
),
function metaReducer(state = defaultState, action) {
if (action.meta && action.meta.panesMap) {
@@ -211,11 +177,11 @@ export default function createPanesAspects({ createPanesMap }) {
panesMap,
panes,
panesByName: panes.reduce((panes, { name }, index) => {
const dividerLeft = getDividerLeft(numOfPanes, index);
const dividerLeft = utils.getDividerLeft(numOfPanes, index);
panes[name] = {
name,
dividerLeft,
isHidden: false
isHidden: name === 'Map' ? state.isMapPaneHidden : false
};
return panes;
}, {})
@@ -241,7 +207,7 @@ export default function createPanesAspects({ createPanesMap }) {
panesByName: state.panes.reduce(
(panesByName, { name }, index) => {
if (!panesByName[name].isHidden) {
const dividerLeft = getDividerLeft(
const dividerLeft = utils.getDividerLeft(
numOfPanes,
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 = {
close: () => dispatch(closeChallengeModal()),
handleKeypress: (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.meta)) {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
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 { isJSEnabledSelector } from './redux';
import {Alert} from 'react-bootstrap';
const mainId = 'fcc-main-frame';
@@ -23,9 +25,12 @@ export class Preview extends PureComponent {
<div className={ `${ns}-preview` }>
{
!isJSEnabled && (
<span className={ `${ns}-preview-js-warning` }>
<Alert
bsStyle='info'
className={ `${ns}-preview-js-warning`}
>
JavaScript is disabled. Execute code to enable
</span>
</Alert>
)
}
<iframe

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,20 @@ import {
const htmlCatch = '\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 }) => `
<body style='margin:8px;'>
<!-- fcc-start-source -->
@@ -28,7 +42,7 @@ const defaultTemplate = ({ source }) => `
`;
const wrapInScript = partial(transformContents, (content) => (
`${htmlCatch}<script>${content}${jsCatch}</script>`
`${htmlCatch}<script>${loopProtector}${content}${jsCatch}</script>`
));
const wrapInStyle = partial(transformContents, (content) => (
`${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 noop from 'lodash/noop';
import bugEpic from './bug-epic';
import modalEpic from './modal-epic';
import completionEpic from './completion-epic.js';
import challengeEpic from './challenge-epic.js';
import executeChallengeEpic from './execute-challenge-epic.js';
@@ -44,7 +44,7 @@ const challengeToFilesMetaCreator =
_.flow(challengeToFiles, createFilesMetaCreator);
export const epics = [
bugEpic,
modalEpic,
challengeEpic,
codeStorageEpic,
completionEpic,
@@ -83,6 +83,12 @@ export const types = createTypes([
'openIssueSearch',
'createIssue',
// help
'openHelpModal',
'closeHelpModal',
'createQuestion',
'openHelpChatRoom',
// panes
'toggleClassicEditor',
'toggleMain',
@@ -157,6 +163,12 @@ export const closeBugModal = createAction(types.closeBugModal);
export const openIssueSearch = createAction(types.openIssueSearch);
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
export const storedCodeFound = createAction(
types.storedCodeFound,
@@ -174,6 +186,7 @@ const initialUiState = {
output: null,
isChallengeModalOpen: false,
isBugOpen: false,
isHelpOpen: false,
successMessage: 'Happy Coding!'
};
@@ -206,6 +219,7 @@ export const challengeModalSelector =
state => getNS(state).isChallengeModalOpen;
export const bugModalSelector = state => getNS(state).isBugOpen;
export const helpModalSelector = state => getNS(state).isHelpOpen;
export const challengeRequiredSelector = state =>
challengeSelector(state).required || [];
@@ -318,9 +332,10 @@ export default combineReducers(
...state,
output: (state.output || '') + output
}),
[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,
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
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) => `
<base href='/' target='_blank'/>
<script>
window.__frameId = '${id}';
window.onerror = function(msg, url, ln, col, err) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,11 +19,16 @@ import {
getPort
} from '../../server/utils/url-utils.js';
const debug = debugFactory('fcc:user:remote');
const debug = debugFactory('fcc:models:user');
const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
const createEmailError = () => new Error(
'Please check to make sure the email is a valid email address.'
const createEmailError = redirectTo => wrapHandledError(
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) {
@@ -76,13 +81,30 @@ function getWaitPeriod(ttl) {
const lastEmailSentAt = moment(new Date(ttl || null));
const isWaitPeriodOver = ttl ?
lastEmailSentAt.isBefore(fiveMinutesAgo) : true;
if (!isWaitPeriodOver) {
const minutesLeft = 5 -
(moment().minutes() - lastEmailSentAt.minutes());
return minutesLeft;
}
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) {
// set salt factor for passwords
User.settings.saltWorkFactor = 5;
@@ -108,85 +130,87 @@ module.exports = function(User) {
User.findOne$ = Observable.fromNodeCallback(User.findOne, User);
User.update$ = Observable.fromNodeCallback(User.updateAll, 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
);
});
User.beforeRemote('create', function({ req }) {
const body = req.body;
// note(berks): we now require all new users to supply an email
// this was not always the case
if (
typeof body.email !== 'string' ||
!isEmail(body.email)
) {
return Promise.reject(createEmailError());
}
// assign random username to new users
// actual usernames will come from github
body.username = 'fcc' + uuid.v4();
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;
User.observe('before save', function(ctx) {
const beforeCreate = Observable.of(ctx)
.filter(({ isNewInstance }) => isNewInstance)
// User.create
.map(({ instance }) => instance)
.flatMap(user => {
// note(berks): we now require all new users to supply an email
// this was not always the case
if (
typeof user.email !== 'string' ||
!isEmail(user.email)
) {
throw createEmailError();
}
const err = wrapHandledError(
new Error('user already exists'),
{
redirectTo: '/email-signin',
message: dedent`
The ${body.email} email address is already associated with an account.
Try signing in with it here instead.
`
}
);
throw err;
});
});
// assign random username to new users
// actual usernames will come from github
// use full uuid to ensure uniqueness
user.username = 'fcc' + uuid.v4();
User.observe('before save', function({ instance: user }, next) {
if (user) {
// 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) {
user.progressTimestamps = [];
}
if (!user.progressTimestamps) {
user.progressTimestamps = [];
}
if (user.progressTimestamps.length === 0) {
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) {
user.progressTimestamps.push({ timestamp: Date.now() });
}
// this is workaround for preventing a server crash
// we do this on save and on create
// refer strongloop/loopback/#1364
if (user.password === '') {
user.password = null;
}
}
return next();
const updateOrSave = Observable.of(ctx)
// not new
.filter(({ isNewInstance }) => !isNewInstance)
.map(({ instance }) => instance)
// is update or save user
.filter(Boolean)
.do(user => {
// 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)) {
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
@@ -224,163 +248,67 @@ module.exports = function(User) {
});
debug('setting up user hooks');
User.beforeRemote('confirm', function(ctx, _, next) {
if (!ctx.req.query) {
return ctx.res.redirect('/');
}
const uid = ctx.req.query.uid;
const token = ctx.req.query.token;
const redirect = ctx.req.query.redirect;
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('/');
// overwrite lb confirm
User.confirm = function(uid, token, redirectTo) {
return this.findById(uid)
.then(user => {
if (!user) {
throw wrapHandledError(
new Error(`User not found: ${uid}`),
{
// standard oops
type: 'info',
redirectTo
}
);
}
if (!user.verificationToken && !user.emailVerified) {
ctx.req.flash('info', {
msg: dedent`Looks like we have your email. But you haven't
verified it yet, please sign in and request a fresh verification
link.`
});
return ctx.res.redirect(redirect);
if (user.verificationToken !== token) {
throw wrapHandledError(
new Error(`Invalid token: ${token}`),
{
type: 'info',
message: dedent`
Looks like you have clicked an invalid link.
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$({
email: user.newEmail,
emailVerified: true,
emailVerifyTTL: null,
newEmail: null,
emailVerifyTTL: null
})
.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');
verificationToken: null
}).toPromise();
});
});
};
User.beforeRemote('login', function(ctx, notUsed, next) {
const { body } = ctx.req;
if (body && typeof body.email === 'string') {
if (!isEmail(body.email)) {
return next(createEmailError());
}
body.email = body.email.toLowerCase();
}
return next();
});
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';
User.prototype.loginByRequest = function login(req, res) {
const createToken = this.createAccessToken$()
.do(accessToken => {
const config = {
signed: !!req.signedCookies,
maxAge: accessToken.ttl
};
if (accessToken && accessToken.id) {
res.cookie('access_token', accessToken.id, config);
res.cookie('userId', accessToken.userId, config);
}
return res.redirect(redirectTo);
}
req.flash('success', { msg: 'Success! You are now logged in.' });
return res.redirect('/');
});
const updateUser = this.update$({
emailVerified: true,
emailAuthLinkTTL: null,
emailVerifyTTL: null
});
});
User.afterRemoteError('login', function(ctx) {
var res = ctx.res;
var req = ctx.req;
req.flash('danger', {
msg: 'Invalid username or password.'
});
return res.redirect('/email-signin');
});
return Observable.combineLatest(
createToken,
updateUser,
req.logIn(this),
(accessToken) => accessToken,
);
};
User.afterRemote('logout', function(ctx, result, next) {
var res = ctx.res;
@@ -482,142 +410,141 @@ module.exports = function(User) {
}
);
User.requestAuthEmail = function requestAuthEmail(email) {
if (!isEmail(email)) {
return Promise.reject(
new Error('The submitted email not valid.')
);
}
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.prototype.createAuthToken = function createAuthToken({ ttl } = {}) {
return Observable.fromNodeCallback(
this.authTokens.create.bind(this.authTokens)
)({ ttl });
};
User.remoteMethod(
'requestAuthEmail',
{
description: 'request a link on email with temporary token to sign in',
accepts: [{
arg: 'email', type: 'string', required: true
}],
returns: [{
arg: 'message', type: 'string'
}],
http: {
path: '/request-auth-link', verb: 'POST'
User.prototype.getEncodedEmail = function getEncodedEmail() {
if (!this.email) {
return null;
}
return Buffer(this.email).toString('base64');
};
User.decodeEmail = email => Buffer(email, 'base64').toString();
User.prototype.requestAuthEmail = function requestAuthEmail(isSignUp) {
return Observable.defer(() => {
const messageOrNull = getWaitMessage(this.emailAuthLinkTTL);
if (messageOrNull) {
throw wrapHandledError(
new Error('request is throttled'),
{
type: 'info',
message: messageOrNull
}
);
}
}
);
User.prototype.requestUpdateEmail = function requestUpdateEmail(
newEmail
) {
const ownEmail = newEmail === this.email;
if (!isEmail('' + newEmail)) {
debug('invalid email:', newEmail );
return Observable.throw(createEmailError());
}
// email is already associated and verified with this account
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.`
)
);
// create a temporary access token with ttl for 15 minutes
return this.createAuthToken({ ttl: 15 * 60 * 1000 });
})
.flatMap(token => {
let renderAuthEmail = renderSignInEmail;
let subject = 'Login Requested - freeCodeCamp';
if (isSignUp) {
renderAuthEmail = renderSignUpEmail;
subject = 'Account Created - freeCodeCamp';
}
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;
return this.update$({
const data = {
newEmail,
emailVerified,
emailVerifyTTL: new Date()
})
.do(() => {
this.newEmail = newEmail;
this.emailVerified = emailVerified;
this.emailVerifyTTL = new Date();
});
};
return this.update$(data).do(() => Object.assign(this, data));
})
.flatMap(() => {
const mailOptions = {

View File

@@ -16,12 +16,16 @@
}
}
},
"newEmail":{
"newEmail": {
"type": "string"
},
"emailVerifyTTL": {
"type": "date"
},
"emailVerified": {
"type": "boolean",
"default": false
},
"emailAuthLinkTTL": {
"type": "date"
},
@@ -179,6 +183,36 @@
"description": "Campers is full stack certified",
"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": {
"type": "boolean",
"description": "Migrate completedChallenges array to challenge map",
@@ -254,6 +288,14 @@
"type": "hasOne",
"model": "pledge",
"foreignKey": ""
},
"authTokens": {
"type": "hasMany",
"model": "AuthToken",
"foreignKey": "userId",
"options": {
"disableInclude": true
}
}
},
"acls": [
@@ -263,6 +305,32 @@
"principalId": "$everyone",
"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",
"principalType": "ROLE",
@@ -284,13 +352,6 @@
"permission": "ALLOW",
"property": "giveBrowniePoints"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "requestUpdateEmail"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
@@ -304,13 +365,6 @@
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateLanguage"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "requestAuthEmail"
}
],
"methods": {}

View File

@@ -3,5 +3,6 @@
"defaultProfileImage": "https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png",
"donateUrl": "https://www.freecodecamp.org/donate",
"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}`
})
},
logLeval: 'debug',
logLevel: 'info',
files: paths.syncWatch,
port: syncPort,
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": {
"version": "1.16.7",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.7.tgz",
"integrity": "sha512-Obn1/GG0sYsnlAlhhSR1hvYRGBpQT+fzSi2IlGN8emCE4iu6f6xIjaq499B1sa7N9iBLzxyOUBo5bzgJd16BvA==",
"version": "1.16.8",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.8.tgz",
"integrity": "sha512-BdN2PXxOFnTXFcyONPW6t0fHjz2fvRZHVMFpaS0wYr+Y8fWEaNOs4V8LEu/fpzQlMx+ahdndgTaGTwPC+J/EeA==",
"requires": {
"@types/express": "4.0.39",
"@types/node": "8.0.47"
@@ -97,15 +92,15 @@
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.39.tgz",
"integrity": "sha512-dBUam7jEjyuEofigUXCtublUHknRZvcRgITlGsTbFgPvnTwtQUt2NgLakbsf+PsGo/Nupqr3IXCYsOpBpofyrA==",
"requires": {
"@types/body-parser": "1.16.7",
"@types/express-serve-static-core": "4.0.56",
"@types/serve-static": "1.13.0"
"@types/body-parser": "1.16.8",
"@types/express-serve-static-core": "4.11.0",
"@types/serve-static": "1.13.1"
}
},
"@types/express-serve-static-core": {
"version": "4.0.56",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.56.tgz",
"integrity": "sha512-/0nwIzF1Bd4KGwW4lhDZYi5StmCZG1DIXXMfQ/zjORzlm4+F1eRA4c6yJQrt4hqX//TDtPULpSlYwmSNyCMeMg==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.11.0.tgz",
"integrity": "sha512-hOi1QNb+4G+UjDt6CEJ6MjXHy+XceY7AxIa28U9HgJ80C+3gIbj7h5dJNxOI7PU3DO1LIhGP5Bs47Dbf5l8+MA==",
"requires": {
"@types/node": "8.0.47"
}
@@ -121,11 +116,11 @@
"integrity": "sha512-kOwL746WVvt/9Phf6/JgX/bsGQvbrK5iUgzyfwZNcKVFcjAUVSpF9HxevLTld2SG9aywYHOILj38arDdY1r/iQ=="
},
"@types/serve-static": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.0.tgz",
"integrity": "sha512-wvQkePwCDZoyQPGb64DTl2TEeLw54CQFXjY+tznxYYxNcBb4LG40ezoVbMDa0epwE4yogB0f42jCaH0356x5Mg==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz",
"integrity": "sha512-jDMH+3BQPtvqZVIcsH700Dfi8Q3MIcEx16g/VdxjoqiGR/NntekB10xdBpirMKnPe9z2C5cBmL0vte0YttOr3Q==",
"requires": {
"@types/express-serve-static-core": "4.0.56",
"@types/express-serve-static-core": "4.11.0",
"@types/mime": "2.0.0"
}
},
@@ -511,6 +506,12 @@
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
"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": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz",
@@ -1535,6 +1536,18 @@
"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": {
"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",
@@ -1828,6 +1841,12 @@
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=",
"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": {
"version": "0.1.5",
"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",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
@@ -3954,6 +4005,24 @@
"integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
"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": {
"version": "1.0.1",
"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",
"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": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.2.0.tgz",
@@ -4418,15 +4497,6 @@
"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": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz",
@@ -5207,22 +5277,13 @@
}
},
"express-validator": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-3.2.1.tgz",
"integrity": "sha1-RWA+fu5pMYXCGY+969QUkl/9NSQ=",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-4.3.0.tgz",
"integrity": "sha512-EYU+JJ2EoLpcw+GKwbB1K8UGb/w1A70Wf3gD/zE9QScQxeSt8qad93lxGtsLwZFoiYM0EByVoSzHJnskp+eVHQ==",
"requires": {
"@types/bluebird": "3.5.18",
"@types/express": "4.0.39",
"bluebird": "3.5.1",
"lodash": "4.17.4",
"validator": "6.2.1"
},
"dependencies": {
"validator": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.2.1.tgz",
"integrity": "sha1-vFdbeNFb6y4zimZbqVMMf0Ce9mc="
}
"validator": "8.2.0"
}
},
"extend": {
@@ -9076,6 +9137,42 @@
"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": {
"version": "0.0.2",
"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",
"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": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
@@ -10119,6 +10222,12 @@
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
"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": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
@@ -10175,6 +10284,12 @@
"integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=",
"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": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
@@ -12749,9 +12864,9 @@
"dev": true
},
"passport": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.2.2.tgz",
"integrity": "sha1-nDjxe+uSnz2Br3uIOOhDDbhwPys=",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz",
"integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=",
"requires": {
"passport-strategy": "1.0.0",
"pause": "0.0.1"
@@ -13161,6 +13276,12 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"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": {
"version": "6.5.1",
"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": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
@@ -16959,9 +17100,9 @@
}
},
"validator": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
"integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g="
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz",
"integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA=="
},
"value-equal": {
"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": {
"version": "2.0.0-pre-I0Z7U9OV",
"resolved": "https://registry.npmjs.org/weinre/-/weinre-2.0.0-pre-I0Z7U9OV.tgz",

View File

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

View File

@@ -619,6 +619,7 @@
"<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.",
"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:",
"<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>",
@@ -1768,7 +1769,8 @@
"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.",
"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>",
"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>."
],
@@ -2242,4 +2244,4 @@
"ru": {}
}
]
}
}

View File

@@ -8,9 +8,9 @@
"title": "Claim Your Responsive Web Design Certificate",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/GjTPLxI.jpg",
"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",
"An image of the text \"Front End Development 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.",
"//i.imgur.com/cyRVnUa.jpg",
"An image of the text \"Responsive Web Design requirements\"",
"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": [
"isHonest",
"isFrontEndCert"
"isRespWebDesignCert"
],
"apis": [
"/certificate/honest",
"/certificate/verify/front-end"
"/certificate/verify/responsive-web-design"
],
"stepIndex": [
1,
@@ -77,9 +77,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"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.",
"//i.imgur.com/GjTPLxI.jpg",
"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 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",
"Una imagen del texto \"Front End Development 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.",
"//i.imgur.com/cyRVnUa.jpg",
"An image of the text \"Responsive Web Design requirements\"",
"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",
"title": "Divide one Decimal by Another with JavaScript",
"title": "Divide One Decimal by Another with JavaScript",
"description": [
"Now let's divide one decimal by another.",
"<hr>",
@@ -704,7 +704,7 @@
"tests": [
"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(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",
"challengeType": 1,
@@ -5411,7 +5411,7 @@
"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>",
"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.",
"<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."

View File

@@ -4,13 +4,13 @@
"time": "5 minutes",
"challenges": [
{
"id": "587d7b7f367417b2b2512b25",
"id": "5a34371eb8853b934b0d9803",
"title": "Claim Your JavaScript Algorithms and Data Structures Certificate",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"An image of our Front End Development 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.",
"//i.imgur.com/EzMrezJ.jpg",
"An image of our JavaScript Algorithms and Data Structures Certificate",
"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",
"An image of the text \"Front End Development 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.",
"//i.imgur.com/rx2gKfB.jpg",
"An image of the text \"JavaScript Algorithms and Data Structures requirements\"",
"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",
"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": [
"isHonest",
"isFrontEndCert"
"isJsAlgoDataStructCert"
],
"apis": [
"/certificate/honest",
"/certificate/verify/front-end"
"/certificate/verify/javascript-algorithms-data-structures"
],
"stepIndex": [
1,
@@ -49,170 +49,34 @@
}
],
"tests": [
{
"id": "a202eed8fc186c8434cb6d61",
"title": "Reverse a String"
},
{
"id": "a302f7aae1aa3152a5b413bc",
"title": "Factorialize a Number"
},
{
"id": "aaa48de84e1ecc7c742e1124",
"title": "Check for Palindromes"
},
{
"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"
"title": "Palindrome Checker"
},
{
"id": "a7f4d8f2483413a6ce226cac",
"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",
"title": "Caesars Cipher"
},
{
"id": "a2f1d72d9b908d0bd72bb9f6",
"title": "Make a Person"
"id": "aff0395860f5d3034dc0bfc9",
"title": "Telephone Number Validator"
},
{
"id": "af4afb223120f7348cdfc9fd",
"title": "Map the Debris"
"id": "aa2e6f85cab2ab736c9a9b24",
"title": "Cash Register"
}
],
"type": "Waypoint",
"challengeType": 7,
"descriptionEs": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/EzMrezJ.jpg",
"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",
"Una imagen del texto \"Front End Development 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.",
"//i.imgur.com/rx2gKfB.jpg",
"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. Click the button below to verify this.",
"#"
],
[

View File

@@ -126,7 +126,7 @@
""
],
"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[\\( ]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",
"title": "Explore Problems with the var Keyword",
"title": "Explore Differences Between the var and let Keywords",
"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.",
"<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.",
"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.",
"<blockquote>console.log(camper);<br>var camper = 'David';<br>// logs undefined</blockquote>",
"The code runs in the following order:",
"<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>",
"This code will run without an error.",
"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.",
"Let's try using the <code>let</code> keyword.",
"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>.",
"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 behavior does not throw an error, searching and fixing bugs becomes more difficult.<br>",
"A new keyword called <code>let</code> was introduced in ES6 to solve this potential issue with the <code>var</code> keyword.",
"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.",
"<blockquote>let camper = 'James';<br>let camper = 'David'; // throws an error</blockquote>",
"This error can be seen in the console of your browser.",
"So unlike <code>var</code>, when using <code>let</code>, a variable with the same name can only be declared once.",
"<hr>",
"Fix the code so that it only uses the <code>let</code> keyword and makes the errors go away.",
"<strong>Note</strong><br>Remember to add <code>\"use strict\";</code> to the top of your code."
"Update the code so it only uses the <code>let</code> keyword.",
"<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": [
"var favorite = redNosedReindeer + \" is Santa's favorite reindeer.\";",
"var redNosedReindeer = \"Rudolph\";",
"var redNosedReindeer = \"Comet\";"
"var catName;",
"var quote;",
"function catTalk() {",
" \"use strict\";",
"",
" catName = \"Oliver\";",
" quote = catName + \" says Meow!\";",
"",
"}",
"catTalk();"
],
"tests": [
"assert(redNosedReindeer === \"Rudolph\", 'message: <code>redNosedReindeer</code> should be Rudolph.');",
"assert(favorite === \"Rudolph is Santa's favorite reindeer.\", \"message: <code>favorite</code> should return Santa's favorite reindeer.\");"
"getUserInput => assert(!getUserInput('index').match(/var/g),'message: <code>var</code> does not exist in code.');",
"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",
"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.",
"<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.",
"<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."
],
"challengeSeed": [
"",
"function checkScope() {",
"\"use strict\";",
" var i = \"function scope\";",
" if (true) {",
" i = \"block scope\";",
@@ -84,13 +93,11 @@
" }",
" console.log(\"Function scope i is: \", i);",
" return i;",
"}",
"// only change the code above this line",
"checkScope();"
"}"
],
"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)');",
"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(/var/g),'message: <code>var</code> does not exist in code.');",
"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\"');"
],
"type": "waypoint",
@@ -107,27 +114,28 @@
"<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>).",
"<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.",
"<strong>Note</strong><br>Don't forget to add <code>\"use strict\";</code> to the top of your code."
"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"
],
"challengeSeed": [
"// change 'var' to 'let' or 'const'",
"// rename constant variables",
"var pi = 3.14;",
"var radius = 10;",
"var calculateCircumference = function(r) {",
" var diameter = 2 * r;",
" var result = pi * diameter;",
" return result;",
"};",
"// Test your code",
"console.log(calculateCircumference(radius));"
"function printManyTimes(str) {",
" \"use strict\";",
"",
" // change code below this line",
"",
" var sentence = str + \" is cool!\";",
" for(var i = 0; i < str.length; i+=2) {",
" console.log(str2);",
" }",
"",
" // change code above this line",
"",
"}",
"printManyTimes(\"FreeCodeCamp\");"
],
"tests": [
"// Test user replaced all var keyword",
"// Test PI is const",
"// Test calculateCircumference is const",
"// Test pi and calculateCircumference has been renamed"
"getUserInput => assert(!getUserInput('index').match(/var/g),'message: <code>var</code> does not exist in code.');",
"getUserInput => assert(getUserInput('index').match(/(const SENTENCE)/g), 'message: <code>SENTENCE</code> should be a constant variable (by using <code>const</code>).');",
"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>).');"
],
"type": "waypoint",
"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>",
"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>",
"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."
"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."
],
"challengeSeed": [
"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",
"// Test your code",
"console.log(s);"
" // change code above this line",
"}",
"editInPlace();"
],
"tests": [
"assert(code.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>.');",
"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/g), 'message: Do not replace <code>const</code> keyword.');",
"getUserInput => assert(getUserInput('index').match(/const\\s+s/g), 'message: <code>s</code> should be a constant variable (by using <code>const</code>).');",
"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>.');"
],
"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."
],
"challengeSeed": [
"const MATH_CONSTANTS = {",
" PI: 3.14",
"};",
"// change code below this line",
"function freezeObj() {",
" \"use strict\";",
" const MATH_CONSTANTS = {",
" PI: 3.14",
" };",
" // change code below this line",
"",
"",
"// change code above this line",
"MATH_CONSTANTS.PI = 99;",
"// Test your code",
"console.log(MATH_CONSTANTS.PI);// should show 3.14"
" // change code above this line",
" try {",
" MATH_CONSTANTS.PI = 99;",
" } catch( ex ) {",
" console.log(ex);",
" }",
" return MATH_CONSTANTS.PI;",
"}",
"const PI = freezeObj();"
],
"tests": [
"// Do not replace <code>const</code> keyword.",
"// <code>MATH_CONSTANTS</code> is declared with <code>const</code>.",
"// 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>.');"
"getUserInput => assert(getUserInput('index').match(/const/g), 'message: Do not replace <code>const</code> keyword.');",
"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>).');",
"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(PI === 3.14, 'message: <code>PI</code> equals <code>3.14</code>.');"
],
"type": "waypoint",
"releasedOn": "Aug 12, 2017",
@@ -214,26 +230,20 @@
"<blockquote>const myFunc= () => \"value\"</blockquote>",
"This code will still return <code>value</code> by default.",
"<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>.",
"Note",
"Don't forget to use strict mode."
"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>."
],
"challengeSeed": [
"// change code below this line",
"var magic = function() {",
" \"use strict\";",
" return new Date();",
"}",
"// change code above this line",
"// test your code",
"console.log(magic());"
"};"
],
"tests": [
"// Test user did replace var keyword",
"// Test magic is const",
"// Test magic is a function",
"// Test magic() returns the correct date",
"// Test function keyword was not used",
"// Test arrow => was used"
"getUserInput => assert(!getUserInput('index').match(/var/g), 'message: User did replace <code>var</code> keyword.');",
"getUserInput => assert(getUserInput('index').match(/const\\s+magic/g), 'message: <code>magic</code> should be a constant variable (by using <code>const</code>).');",
"assert(typeof magic === 'function', 'message: <code>magic</code> is a <code>function</code>.');",
"assert(magic().getDate() == new Date().getDate(), 'message: <code>magic()</code> returns correct date.');",
"getUserInput => assert(!getUserInput('index').match(/function/g), 'message: <code>function</code> keyword was not used.');"
],
"type": "waypoint",
"releasedOn": "Feb 17, 2017",
@@ -248,26 +258,22 @@
"<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.",
"<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.",
"Note",
"Don't forget to use strict mode."
"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."
],
"challengeSeed": [
"// change code below this line",
"var myConcat = function(arr1, arr2) {",
" \"use strict\";",
" return arr1.concat(arr2);",
"}",
"// change code above this line",
"};",
"// test your code",
"console.log(myConcat([1, 2], [3, 4, 5]));"
],
"tests": [
"// Test user did replace var keyword",
"// Test myConcat is const",
"assert(typeof myConcat === \"function\", 'message: <code>myConcat</code> should be a function');",
"// Test myConcat() returns the correct array",
"// Test function keyword was not used",
"// Test arrow => was used"
"getUserInput => assert(!getUserInput('index').match(/var/g), 'message: User did replace <code>var</code> keyword.');",
"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(() => { const a = myConcat([1], [2]); return a[0] == 1 && a[1] == 2; }, 'message: <code>myConcat()</code> returns the correct <code>array</code>');",
"getUserInput => assert(!getUserInput('index').match(/function/g), 'message: <code>function</code> keyword was not used.');"
],
"type": "waypoint",
"releasedOn": "Feb 17, 2017",
@@ -286,27 +292,29 @@
"<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.",
"<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>.",
"Note",
"Don't forget to use strict mode."
"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>."
],
"challengeSeed": [
"const realNumberArray = [4, 5.6, -9.8, 3.14, 42, 6, 8.34];",
"// change code below this line",
"var squaredIntegers = realNumberArray;",
"// change code above this line",
"const squareList = (arr) => {",
" \"use strict\";",
" // change code below this line",
" const squaredIntegers = arr;",
" // change code above this line",
" return squaredIntegers;",
"};",
"// test your code",
"const squaredIntegers = squareList(realNumberArray);",
"console.log(squaredIntegers);"
],
"tests": [
"// Test user did replace <code>var</code> keyword",
"// Test <code>squaredIntegers</code> is <code>const</code>",
"assert(Array.isArray(squaredIntegers), 'message: <code>squaredIntegers</code> should be an array');",
"getUserInput => assert(!getUserInput('index').match(/var/g), 'message: User did replace <code>var</code> keyword.');",
"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 <code>array</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",
"// Test arrow <code>=></code> was used",
"assert(!code.match(/(for)|(while)/g), 'message: loop should not be used');",
"assert(code.match(/map/g) && code.match(/filter/g), 'message: <code>map</code> and <code>filter</code> should be used');"
"getUserInput => assert(!getUserInput('index').match(/function/g), 'message: <code>function</code> keyword was not used.');",
"getUserInput => assert(!getUserInput('index').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');"
],
"type": "waypoint",
"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>",
"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>",
"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."
"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."
],
"challengeSeed": [
"function increment(number, value) {",
"const increment = (function() {",
" \"use strict\";",
" return function increment(number, value) {",
" return number + value;",
"}",
" };",
"})();",
"console.log(increment(5, 2)); // returns 7",
"console.log(increment(5)); // returns NaN"
],
"tests": [
"assert(increment(5, 2) === 7, \"The result of increment(5, 2) should be 7\");",
"assert(increment(5) === 6, \"The result of increment(5) should be 6\");",
"// Test default parameter was used for 'value'"
"assert(increment(5, 2) === 7, 'message: The result of <code>increment(5, 2)</code> should be <code>7</code>.');",
"assert(increment(5) === 6, 'message: The result of <code>increment(5)</code> should be <code>6</code>.');",
"getUserInput => assert(getUserInput('index').match(/value\\s*=\\s*1/g), 'message: default parameter <code>1</code> was used for <code>value</code>.');"
],
"type": "waypoint",
"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>",
"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>",
"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."
"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."
],
"challengeSeed": [
"function sum(x, y, z) {",
"const sum = (function() {",
" \"use strict\";",
" return function sum(x, y, z) {",
" const array = [ x, y, z ];",
" return array.reduce((a, b) => a + b, 0);",
"}",
" };",
"})();",
"console.log(sum(1, 2, 3)); // 6"
],
"tests": [
"assert(sum(0,1,2) === 3, 'The result of sum(0,1,2) should be 3');",
"assert(sum(1,2,3,4) === 10, 'The result of sum(1,2,3,4) should be 10');",
"assert(sum(5) === 5, 'The result of sum(5) should be 5');",
"assert(sum() === 0, 'The result of sum() should be 0');"
"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, 'message: The result of <code>sum(1,2,3,4)</code> should be 10');",
"assert(sum(5) === 5, 'message: The result of <code>sum(5)</code> should be 5');",
"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",
"releasedOn": "Feb 17, 2017",
@@ -390,14 +403,17 @@
],
"challengeSeed": [
"const arr1 = ['JAN', 'FEB', 'MAR', 'APR', 'MAY'];",
"const arr2 = []; // change this line",
"arr1.push('JUN');",
"console.log(arr2); // arr2 should not be affected"
"let arr2;",
"(function() {",
" \"use strict\";",
" arr2 = []; // change this line",
"})();",
"console.log(arr2);"
],
"tests": [
"// Test arr2 is correct copy of arr1",
"// Test arr1 has changed",
"// Test spread operator was used"
"assert(arr2.every((v, i) => v === arr1[i]), 'message: <code>arr2</code> is correct copy of <code>arr1</code>.');",
"getUserInput => assert(getUserInput('index').match(/\\[\\s*...arr1\\s*\\]/g),'message: <code>...</code> spread operator was used to duplicate <code>arr1</code>.');",
"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",
"releasedOn": "Feb 17, 2017",
@@ -408,7 +424,7 @@
"id": "587d7b89367417b2b2512b49",
"title": "Use Destructuring Assignment to Assign Variables from Objects",
"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.",
"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>",
@@ -418,18 +434,26 @@
"<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.",
"<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": [
"const greeting = 'itadakimasu';",
"// change code below this line",
"const length = 0; // change this",
"// change code above this line",
"console.log(length); // should be using destructuring"
"function getLength(str) {",
" \"use strict\";",
"",
" // change code below this line",
" 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": [
"// Test len is 11",
"// Test destructuring was used"
"assert(typeof getLength('') === 'number', 'message: the function <code>getLength()</code> returns a number.');",
"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",
"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>."
],
"challengeSeed": [
"const forecast = {",
"const LOCAL_FORECAST = {",
" today: { min: 72, max: 83 },",
" tomorrow: { min: 73.3, max: 84.6 }",
"};",
"// change code below this line",
"const maxOfTomorrow = undefined; // change this line",
"// change code above this line",
"console.log(maxOfTomorrow); // should be 84.6"
"",
"function getMaxOfTmrw(forecast) {",
" \"use strict\";",
" // 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": [
"// Test maxOfTomorrow to be 84.6",
"// Test destructuring was used"
"assert(getMaxOfTmrw(LOCAL_FORECAST) === 84.6, 'message: <code>maxOfTomorrow</code> equals <code>84.6</code>');",
"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",
"releasedOn": "Feb 17, 2017",
@@ -482,9 +512,12 @@
],
"challengeSeed": [
"let a = 8, b = 6;",
"// change code below this line",
"",
"// change code above this line",
"(() => {",
" \"use strict\";",
" // change code below this line",
" ",
" // change code above this line",
"})();",
"console.log(a); // should be 6",
"console.log(b); // should be 8"
],
@@ -512,17 +545,21 @@
],
"challengeSeed": [
"const source = [1,2,3,4,5,6,7,8,9,10];",
"// change code below this line",
"const arr = source; // change this",
"// change code below this line",
"function removeFirstTwo(list) {",
" \"use strict\";",
" // 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(source); // should be [1,2,3,4,5,6,7,8,9,10];"
],
"tests": [
"// Test arr is [3,4,5,6,7,8,9,10];",
"// Test source is [1,2,3,4,5,6,7,8,9,10];",
"// Test destructuring was used",
"// Test slice was not used"
"assert(arr.every((v, i) => v === i + 3),'message: <code>arr</code> should be <code>[3,4,5,6,7,8,9,10]</code>');",
"getUserInput => assert(getUserInput('index').match(/\\[\\s*\\w\\s*,\\s*\\w\\s*,\\s*...arr\\s*\\]/g),'message: destructuring was used.');",
"getUserInput => assert(!getUserInput('index').match(/Array.slice/g), 'message: <code>Array.slice()</code> was not used.');"
],
"type": "waypoint",
"releasedOn": "Feb 17, 2017",
@@ -552,16 +589,24 @@
" min: -0.75,",
" average: 35.85",
"};",
"// change code below this line",
"const half = (stats) => ((stats.max + stats.min) / 2.0); // use function argument destructurung",
"// change code above this line",
"const half = (function() {",
" \"use strict\"; // do not change 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(half(stats)); // should be 28.015"
],
"tests": [
"// Test stats is an object",
"// Test half is 28.015",
"// Test destructuring was used"
"assert(typeof stats === 'object', 'message: <code>stats</code> should be an <code>object</code>.');",
"assert(half(stats) === 28.015, 'message: <code>half(stats)</code> should be <code>28.015</code>');",
"getUserInput => assert(getUserInput('index').match(/\\(\\s*\\{\\s*\\w+\\s*,\\s*\\w+\\s*\\}\\s*\\)/g), 'message: Destructuring was used.');"
],
"type": "waypoint",
"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.",
"This new way of creating strings gives you more flexibility to create robust strings.",
"<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": [
"const result = {",
@@ -588,21 +633,27 @@
" failure: [\"no-var\", \"var-on-top\", \"linebreak\"],",
" skipped: [\"id-blacklist\", \"no-dup-keys\"]",
"};",
"// change code below this line",
"const resultDisplay = null;",
"// change code above this line",
"console.log(resultDisplay);",
"function makeList(arr) {",
" \"use strict\";",
"",
" // change code below this line",
" const resultDisplayArray = null;",
" // change code above this line",
"",
" return resultDisplayArray;",
"}",
"/**",
" * should look like this",
" * <li class=\"text-warning\">no-var</li>",
" * <li class=\"text-warning\">var-on-top</li>",
" * <li class=\"text-warning\">linebreak</li>",
" **/"
" * makeList(result.failure) should return:",
" * [ <li class=\"text-warning\">no-var</li>,",
" * <li class=\"text-warning\">var-on-top</li>, ",
" * <li class=\"text-warning\">linebreak</li> ]",
" **/",
"const resultDisplayArray = makeList(result.failure);"
],
"tests": [
"// Test resultDisplay is a string",
"// Test resultDisplay is the desired output",
"// Test template strings were used"
"assert(typeof makeList(result.failure) === 'object' && resultDisplayArray.length === 3, 'message: <code>resultDisplayArray</code> is a list containing <code>result failure</code> messages.');",
"assert(makeList(result.failure).every((v, i) => v === `<li class=\"text-warning\">${result.failure[i]}</li>`), 'message: <code>resultDisplayArray</code> is the desired output.');",
"getUserInput => assert(getUserInput('index').match(/\\`<li class=\"text-warning\">\\$\\{\\w+\\}<\\/li>\\`/g), 'message: Template strings were used');"
],
"type": "waypoint",
"releasedOn": "Feb 17, 2017",
@@ -624,20 +675,21 @@
"Use simple fields with object literals to create and return a <code>Person</code> object."
],
"challengeSeed": [
"// change code below this line",
"const createPerson = (name, age, gender) => {",
" \"use strict\";",
" // change code below this line",
" return {",
" name: name,",
" age: age,",
" gender: gender",
" };",
" // change code above this line",
"};",
"// change code above this line",
"console.log(createPerson(\"Zodiac Hasbro\", 56, \"male\")); // returns a proper object"
],
"tests": [
"// Test the output is {name: \"Zodiac Hasbro\", age: 56, gender: \"male\"}",
"// Test no : was present"
"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>.');",
"getUserInput => assert(!getUserInput('index').match(/:/g), 'message: No <code>:</code> were used.');"
],
"type": "waypoint",
"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."
],
"challengeSeed": [
"// change code below this line",
"const bicycle = {",
" gear: 2,",
" setGear: function(newGear) {",
" \"use strict\";",
" this.gear = newGear;",
" }",
"};",
@@ -668,8 +722,8 @@
"console.log(bicycle.gear);"
],
"tests": [
"// Test the output is Sending request to Yanoshi Mimoto",
"// Test no : was present"
"assert(() => { bicycle.setGear(48); return bicycle.gear === 48 }, 'message: <code>setGear</code> is a function and changes the <code>gear</code> variable.');",
"getUserInput => assert(!getUserInput('index').match(/:\\s*function\\s*\\(\\)/g), 'message: Declarative function was used.');"
],
"type": "waypoint",
"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."
],
"challengeSeed": [
"/* Alter code below this line */",
"const Vegetable = undefined;",
"/* Alter code above this line */",
"function makeClass() {",
" \"use strict\";",
" /* Alter code below this line */",
"",
" /* Alter code above this line */",
" return Vegetable;",
"}",
"const Vegetable = makeClass();",
"const carrot = new Vegetable('carrot');",
"console.log(carrot.name); // => should be 'carrot'"
],
"tests": [
"// Test the Vegetable is a class",
"// Test that class keyword was used",
"// Test that other objects could be created with the 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.');",
"getUserInput => assert(getUserInput('index').match(/class/g),'message: <code>class</code> keyword was used.');",
"assert(() => {const a = new Vegetable(\"apple\"); return typeof a === 'object';},'message: <code>Vegetable</code> can be instantiated.');"
],
"type": "waypoint",
"releasedOn": "Feb 17, 2017",
@@ -722,25 +781,30 @@
"<hr>",
"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.",
"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",
"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.",
"In other words, you are abstracting implementation details from the consumer."
],
"challengeSeed": [
"/* Alter code below this line */",
"const Thermostat = undefined;",
"/* Alter code above this line */",
"function makeClass() {",
" \"use strict\";",
" /* Alter code below this line */",
"",
" /* Alter code above this line */",
" return Thermostat;",
"}",
"const Thermostat = makeClass();",
"const thermos = new Thermostat(76); // setting in Farenheit scale",
"let temp = thermos.temperature; // 24.44 in C",
"thermos.temperature = 26;",
"temp = thermos.temperature; // 26 in C"
],
"tests": [
"// Test the Thermostat is a class",
"// Test that class keyword was used",
"// Test that other objects could be created with the 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.');",
"getUserInput => assert(getUserInput('index').match(/class/g),'message: <code>class</code> keyword was used.');",
"assert(() => {const t = new Thermostat(32); return typeof t === 'object' && t.temperature === 0;}, 'message: <code>Thermostat</code> can be instantiated.');"
],
"type": "waypoint",
"releasedOn": "Feb 17, 2017",
@@ -758,16 +822,25 @@
"A description of the above code:",
"<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.",
"<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 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>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>",
"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": [
"\"use strict\";",
"capitalizeString(\"hello!\");"
],
"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",
"releasedOn": "Feb 17, 2017",
@@ -787,13 +860,17 @@
"<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."
],
"head": [
"window.exports = function(){};"
],
"challengeSeed": [
"\"use strict\";",
"const foo = \"bar\";",
"const boo = \"far\";"
],
"tests": [
"assert(code.match(/export\\s+const\\s+foo\\s+=+\\s\"bar\"/ig))",
"assert(code.match(/export\\s+const\\s+boo\\s+=+\\s\"far\"/ig))"
"getUserInput => assert(getUserInput('index').match(/export\\s+const\\s+foo\\s+=+\\s\"bar\"/g), 'message: <code>foo</code> is exported.');",
"getUserInput => assert(getUserInput('index').match(/export\\s+const\\s+boo\\s+=+\\s\"far\"/g), 'message: <code>bar</code> is exported.');"
],
"type": "waypoint",
"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>",
"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>",
"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>",
"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": [
"\"use strict\";",
"myStringModule.capitalize(\"foo\");",
"myStringModule.lowercase(\"Foo\");"
],
"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",
"releasedOn": "Feb 17, 2017",
@@ -837,11 +923,15 @@
"<hr>",
"The following function should be the fallback value for the module. Please add the necessary code to do so."
],
"head": [
"window.exports = function(){};"
],
"challengeSeed": [
"\"use strict\";",
"function subtract(x,y) {return x - y;}"
],
"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",
"releasedOn": "Feb 17, 2017",
@@ -859,11 +949,19 @@
"<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."
],
"head": [
"window.require = function(str) {",
"if (str === 'math_functions') {",
"return function(a, b) {",
"return a - b;",
"}}};"
],
"challengeSeed": [
"\"use strict\";",
"subtract(7,4);"
],
"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",
"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",
"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",
"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",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/vOtZumH.jpg",
"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",
"An image of the text \"Front End Development 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.",
"//i.imgur.com/GJeTCMS.jpg",
"An image of the text \"Front End Libraries Certificate requirements\"",
"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": [
"isHonest",
"isFrontEndCert"
"isFrontEndLibsCert"
],
"apis": [
"/certificate/honest",
"/certificate/verify/front-end"
"/certificate/verify/front-end-libraries"
],
"stepIndex": [
1,
@@ -106,9 +106,9 @@
"title": "Solicite seu Certificado de Bibliotecas Front End",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/vOtZumH.jpg",
"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,
"time": "150 hours",
"helpRoom": "Help",

View File

@@ -50,7 +50,7 @@
},
"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.');",
"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>.');"
],
"solutions": [
@@ -258,7 +258,7 @@
},
"tests": [
"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('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"
}
],
"template": "<body><div id='root'></div>${ source }</body>",
"template": "<body><div id='root'></div><div id='challenge-node'></div>${ source }</body>",
"challenges": [
{
"id": "587d7dbc367417b2b2512bb1",
@@ -143,11 +143,7 @@
"releasedOn": "December 25, 2017",
"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.",
"ReactDOM offers a simple method to render React elements to the DOM which looks like this: <code>ReactDOM.render(componentToRender, targetNode)</code>.",
"<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>",
"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.",
"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>",
"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",
""
],
"tail": "ReactDOM.render(JSX, document.getElementById('root'))"
]
}
},
"tests": [
@@ -251,9 +246,9 @@
" remove comment and change code above this line */}",
" </div>",
");",
"",
"ReactDOM.render(JSX, document.getElementById('root'));"
]
""
],
"tail": "ReactDOM.render(JSX, document.getElementById('root'))"
}
},
"tests": [
@@ -680,8 +675,7 @@
"",
"// change code below this line",
""
],
"tail": "ReactDOM.render(<TypesOfFood />, document.getElementById('root'))"
]
}
},
"tests": [
@@ -716,8 +710,7 @@
"contents": [
"// change code below this line",
""
],
"tail": "ReactDOM.render(<MyComponent />, document.getElementById('root'))"
]
}
},
"tests": [
@@ -1129,8 +1122,8 @@
"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('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 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 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(/\\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.');"
],
"solutions": [
@@ -1235,7 +1228,7 @@
"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(/<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": [
"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(/<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>.');",
"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": [
"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": [
"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.');",
"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>.'); }; "
],
"solutions": [
@@ -1658,9 +1651,9 @@
"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(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: '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": [
"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": [
"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>",
"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."
],
"files": {
@@ -1917,7 +1910,7 @@
"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 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": [
"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": [
"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(2).name(), 'button', \"message: <code>MagicEightBall</code>'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.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>&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>&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.');",
"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.');",
"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.'); }; ",
"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": [
"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.');",
"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.'); }; ",
"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": [
"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": [
"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)).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.\");",
"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 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>.); }; ",
"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>&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>&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>&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>.'); }; ",
"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:",
"<code>let onlineUsers = users.filter(user => user.online);",
"<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": {
"indexjsx": {
@@ -2903,9 +2896,9 @@
},
"tests": [
"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 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.');"
],
"solutions": [

View File

@@ -195,7 +195,7 @@
"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(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": [
"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() { 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() { 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(/ /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('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('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": [
"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() { 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.');",
"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": [
"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(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 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": [
"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",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/N8drT4I.jpg",
"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",
"An image of the text \"Front End Development 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.",
"#"
"//i.imgur.com/BUaEvDo.jpg",
"An image of the text \"Data Visualization Certificate requirements\"",
"Let's confirm that you have completed data visualisation projects. Click the button below to verify this.", "#"
],
[
"//i.imgur.com/Q5Za9U6.jpg",
@@ -36,11 +35,11 @@
{
"properties": [
"isHonest",
"isFrontEndCert"
"isDataVisCert"
],
"apis": [
"/certificate/honest",
"/certificate/verify/front-end"
"/certificate/verify/data-visualization"
],
"stepIndex": [
1,
@@ -77,10 +76,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/N8drT4I.jpg",
"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",
@@ -89,9 +87,9 @@
"#"
],
[
"//i.imgur.com/14F2Van.jpg",
"Una imagen del texto \"Front End Development 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.",
"//i.imgur.com/BUaEvDo.jpg",
"An image of the text \"Data Visualization Certificate requirements\"",
"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",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/gfH7j5B.jpg",
"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",
"An image of the text \"Front End Development 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.",
"//i.imgur.com/IBTfUzO.jpg",
"An image of the text \"APIs and Microservices Certificate requirements\"",
"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": [
"isHonest",
"isFrontEndCert"
"isApisMicroservicesCert"
],
"apis": [
"/certificate/honest",
"/certificate/verify/front-end"
"/certificate/verify/apis-microservices"
],
"stepIndex": [
1,
@@ -77,9 +77,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/gfH7j5B.jpg",
"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",
"Una imagen del texto \"Front End Development 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.",
"//i.imgur.com/IBTfUzO.jpg",
"An image of the text \"APIs and Microservices Certificate requirements\"",
"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",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/YhKzGLb.jpg",
"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",
"An image of the text \"Front End Development 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.",
"//i.imgur.com/TM4KGfb.jpg",
"An image of the text \"Information Security and Quality Assurance Certificate requirements\"",
"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": [
"isHonest",
"isFrontEndCert"
"isInfosecQaCert"
],
"apis": [
"/certificate/honest",
"/certificate/verify/front-end"
"/certificate/verify/information-security-quality-assurance"
],
"stepIndex": [
1,
@@ -77,9 +77,9 @@
"title": "Reclama tu certificado de Desarrollo de interfaces",
"description": [
[
"//i.imgur.com/k8btNUB.jpg",
"//i.imgur.com/YhKzGLb.jpg",
"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",
"Una imagen del texto \"Front End Development 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.",
"//i.imgur.com/TM4KGfb.jpg",
"An image of the text \"Information Security and Quality Assurance Certificate requirements\"",
"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": "",
"helpRoom": "HelpJavaScript",
"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",
"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",
"title": "Inventory Update",

View File

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

View File

@@ -30,7 +30,7 @@
"<pre><code class='language-javascript'>'ABC'</code></pre>"
],
"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",

View File

@@ -9,7 +9,8 @@
"title": "Show the Local Weather",
"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>.",
"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 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.",
@@ -675,6 +676,29 @@
],
"isRequired": false,
"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 */
require('babel-register');
require('dotenv').load();
var adler32 = require('adler32');
const adler32 = require('adler32');
var Rx = require('rx'),
_ = require('lodash'),
utils = require('../server/utils'),
getChallenges = require('./getChallenges'),
app = require('../server/server');
const Rx = require('rx');
const _ = require('lodash');
const utils = require('../server/utils');
const getChallenges = require('./getChallenges');
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;
var nameify = utils.nameify;
var Observable = Rx.Observable;
var Challenge = app.models.Challenge;
const dasherize = utils.dasherize;
const nameify = utils.nameify;
const Observable = Rx.Observable;
const Challenge = app.models.Challenge;
var destroyChallenges =
const destroyChallenges =
Observable.fromNodeCallback(Challenge.destroyAll, Challenge);
var createChallenges =
const createChallenges =
Observable.fromNodeCallback(Challenge.create, Challenge);
var Block = app.models.Block;
var destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
var createBlocks = Observable.fromNodeCallback(Block.create, Block);
const Block = app.models.Block;
const destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
const createBlocks = Observable.fromNodeCallback(Block.create, Block);
const arrToString = arr =>
Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
@@ -33,28 +38,28 @@ Observable.combineLatest(
.last()
.flatMap(function() { return Observable.from(getChallenges()); })
.flatMap(function(challengeSpec) {
var order = challengeSpec.order;
var blockName = challengeSpec.name;
var superBlock = challengeSpec.superBlock;
var superOrder = challengeSpec.superOrder;
var isBeta = !!challengeSpec.isBeta;
var isComingSoon = !!challengeSpec.isComingSoon;
var fileName = challengeSpec.fileName;
var helpRoom = challengeSpec.helpRoom || 'Help';
var time = challengeSpec.time || 'N/A';
var isLocked = !!challengeSpec.isLocked;
var message = challengeSpec.message;
var required = challengeSpec.required || [];
var template = challengeSpec.template;
const order = challengeSpec.order;
const blockName = challengeSpec.name;
const superBlock = challengeSpec.superBlock;
const superOrder = challengeSpec.superOrder;
const isBeta = !!challengeSpec.isBeta;
const isComingSoon = !!challengeSpec.isComingSoon;
const fileName = challengeSpec.fileName;
const helpRoom = challengeSpec.helpRoom || 'Help';
const time = challengeSpec.time || 'N/A';
const isLocked = !!challengeSpec.isLocked;
const message = challengeSpec.message;
const required = challengeSpec.required || [];
const template = challengeSpec.template;
console.log('parsed %s successfully', blockName);
log('parsed %s successfully', blockName);
// challenge file has no challenges...
if (challengeSpec.challenges.length === 0) {
return Rx.Observable.just([{ block: 'empty ' + blockName }]);
}
var block = {
const block = {
title: blockName,
name: nameify(blockName),
dashedName: dasherize(blockName),
@@ -68,7 +73,7 @@ Observable.combineLatest(
return createBlocks(block)
.map(block => {
console.log('successfully created %s block', block.name);
log('successfully created %s block', block.name);
return challengeSpec.challenges
.map(function(challenge, index) {
@@ -123,11 +128,11 @@ Observable.combineLatest(
})
.subscribe(
function(challenges) {
console.log('%s successfully saved', challenges[0].block);
log('%s successfully saved', challenges[0].block);
},
function(err) { throw err; },
function() {
console.log('challenge seed completed');
log('challenge seed completed');
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) {
// enable authentication
// enable loopback access control authentication. see:
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
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 {
respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
frontEndChallengeId,
dataVisChallengeId,
backEndChallengeId
dataVisId,
apisMicroservicesId,
backEndChallengeId,
infosecQaId
} from '../utils/constantStrings.json';
import {
@@ -60,9 +65,12 @@ function getIdsForCert$(id, Challenge) {
// {
// email: String,
// username: String,
// isFrontEndCert: Boolean,
// isBackEndCert: Boolean,
// isDataVisCert: Boolean
// isRespWebDesignCert: Boolean,
// isFrontEndLibsCert: Boolean,
// isJsAlgoDataStructCert: Boolean,
// isDataVisCert: Boolean,
// isApisMicroservicesCert: Boolean,
// isInfosecQaCert: Boolean
// },
// send$: Observable
// ) => Observable
@@ -71,17 +79,23 @@ function sendCertifiedEmail(
email,
name,
username,
isFrontEndCert,
isBackEndCert,
isDataVisCert
isRespWebDesignCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
},
send$
) {
if (
!isEmail(email) ||
!isFrontEndCert ||
!isBackEndCert ||
!isDataVisCert
!isRespWebDesignCert ||
!isFrontEndLibsCert ||
!isJsAlgoDataStructCert ||
!isDataVisCert ||
!isApisMicroservicesCert ||
!isInfosecQaCert
) {
return Observable.just(false);
}
@@ -107,8 +121,16 @@ export default function certificate(app) {
const certTypeIds = {
[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(
@@ -123,12 +145,42 @@ export default function certificate(app) {
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(
'/certificate/verify/data-visualization',
ifNoUser401,
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(
'/certificate/honest',
sendMessageToNonUser,

View File

@@ -132,7 +132,7 @@ export default function commit(app) {
const {
nonprofit: nonprofitName = 'girl develop it',
amount = '5',
goal = commitGoals.frontEndCert
goal = commitGoals.respWebDesignCert
} = req.query;
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) {
if (process.env.NODE_ENV === 'production') {
return;
}
var explorer;
let explorer;
try {
explorer = require('loopback-component-explorer');
} catch (err) {
// 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.
app.once('started', function() {
console.log(
log(
'Run `npm install loopback-component-explorer` to enable ' +
'the LoopBack explorer'
);
@@ -17,13 +21,13 @@ module.exports = function mountLoopBackExplorer(app) {
return;
}
var restApiRoot = app.get('restApiRoot');
var mountPath = '/explorer';
const restApiRoot = app.get('restApiRoot');
const mountPath = '/explorer';
explorer(app, { basePath: restApiRoot, mountPath });
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 {
frontEndChallengeId,
dataVisChallengeId,
backEndChallengeId
backEndChallengeId,
respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
dataVisId,
apisMicroservicesId,
infosecQaId
} from '../utils/constantStrings.json';
import certTypes from '../utils/certTypes.json';
import {
@@ -24,27 +29,44 @@ import {
import supportedLanguages from '../../common/utils/supported-languages';
import { getChallengeInfo, cachedMap } from '../utils/map';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map');
const certIds = {
[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 = {
[certTypes.frontEnd]: 'certificate/front-end.jade',
[certTypes.dataVis]: 'certificate/data-vis.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 = {
[certTypes.frontEnd]: 'Front End certified',
[certTypes.dataVis]: 'Data Vis 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';
@@ -139,7 +161,7 @@ function buildDisplayChallenges(
module.exports = function(app) {
const router = app.loopback.Router();
const api = app.loopback.Router();
const { AccessToken, Email, User } = app.models;
const { Email, User } = app.models;
const map$ = cachedMap(app.models);
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(
'/delete-my-account',
sendNonUserToMap,
@@ -208,11 +213,6 @@ module.exports = function(app) {
showCert.bind(null, certTypes.frontEnd)
);
api.get(
'/:username/data-visualization-certification',
showCert.bind(null, certTypes.dataVis)
);
api.get(
'/:username/back-end-certification',
showCert.bind(null, certTypes.backEnd)
@@ -223,6 +223,36 @@ module.exports = function(app) {
(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/report-user/',
@@ -240,179 +270,6 @@ module.exports = function(app) {
app.use('/:lang', router);
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) {
const { username } = req.user;
return res.redirect('/' + username);
@@ -586,9 +443,14 @@ module.exports = function(app) {
isLocked: true,
isAvailableForHire: true,
isFrontEndCert: true,
isDataVisCert: true,
isBackEndCert: true,
isFullStackCert: true,
isRespWebDesignCert: true,
isFrontEndLibsCert: true,
isJsAlgoDataStructCert: true,
isDataVisCert: true,
isApisMicroservicesCert: true,
isInfosecQaCert: true,
isHonest: true,
username: true,
name: true,

View File

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

View File

@@ -56,7 +56,8 @@
"./middlewares/jade-helpers": {},
"./middlewares/migrate-completed-challenges": {},
"./middlewares/add-lang": {},
"./middlewares/flash-cheaters": {}
"./middlewares/flash-cheaters": {},
"./middlewares/passport-login": {}
},
"files": {},
"final:after": {
@@ -64,9 +65,9 @@
"./middlewares/error-handlers": {},
"strong-error-handler": {
"params": {
"debug": false,
"log": true
}
"debug": false,
"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 { unwrapHandledError } from '../utils/create-handled-error.js';
export default function prodErrorHandler() {
if (process.env.NODE_ENV === 'development') {
return errorHandler({ log: true });
const isDev = process.env.NODE_ENV !== 'production';
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.
// disabling eslint due to express parity rules for error handlers
return function(err, req, res, next) { // eslint-disable-line
// respect err.status
if (err.status) {
res.statusCode = err.status;
}
// default status code to 500
if (res.statusCode < 400) {
res.statusCode = 500;
const handled = unwrapHandledError(err);
// respect handled error status
let status = handled.status || err.status || res.statusCode;
if (!handled.status && status < 400) {
status = 500;
}
res.status(status);
// parse res type
const accept = accepts(req);
const type = accept.type('html', 'json', 'text');
const handled = unwrapHandledError(err);
const redirectTo = handled.redirectTo || '/map';
const redirectTo = handled.redirectTo || '/';
const message = handled.message ||
'Oops! Something went wrong. Please try again later';
if (isDev) {
console.error(err);
}
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') {
req.flash(
handled.type || 'danger',
@@ -39,6 +89,7 @@ export default function prodErrorHandler() {
} else if (type === 'json') {
res.setHeader('Content-Type', 'application/json');
return res.send({
type: handled.type || 'errors',
message
});
// plain text

View File

@@ -8,7 +8,7 @@ import {
const log = debug('fcc:middlewares:error-reporter');
export default function keymetrics() {
export default function errorHandler() {
if (process.env.NODE_ENV !== 'production') {
return (err, req, res, next) => {
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: {
// Refer : http://stackoverflow.com/a/430240/1932901
trimTags(value) {
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
const tagOrComment = new RegExp(
'<(?:'
// Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name
+ '|/?[a-z]'
+ tagBody
+ ')>',
'gi');
let rawValue;
do {
rawValue = value;
value = value.replace(tagOrComment, '');
} while (value !== rawValue);
return value.replace(/</g, '&lt;');
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
const tagOrComment = new RegExp(
'<(?:'
// Comment body.
+ '!--(?:(?:-*[^->])*--+|-?)'
// Special "raw text" elements whose content should be elided.
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
// Regular name
+ '|/?[a-z]'
+ tagBody
+ ')>',
'gi'
);
let rawValue;
do {
rawValue = value;
value = value.replace(tagOrComment, '');
} while (value !== rawValue);
return value.replace(/</g, '&lt;');
}
}
});

View File

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

View File

@@ -1,4 +1,5 @@
require('dotenv').load();
require('./utils/webpack-code-split-polyfill');
if (process.env.OPBEAT_ID) {
console.log('loading opbeat');
@@ -9,33 +10,23 @@ if (process.env.OPBEAT_ID) {
});
}
var _ = require('lodash'),
Rx = require('rx'),
loopback = require('loopback'),
boot = require('loopback-boot'),
expressState = require('express-state'),
path = require('path'),
setupPassport = require('./component-passport');
const _ = require('lodash');
const Rx = require('rx');
const loopback = require('loopback');
const boot = require('loopback-boot');
const expressState = require('express-state');
const path = require('path');
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';
var app = loopback();
var isBeta = !!process.env.BETA;
const app = loopback();
const isBeta = !!process.env.BETA;
expressState.extend(app);
app.set('state namespace', '__fcc__');
@@ -52,17 +43,36 @@ boot(app, {
setupPassport(app);
const { db } = app.datasources;
db.on('connected', _.once(() => log('db connected')));
app.start = _.once(function() {
app.listen(app.get('port'), function() {
const server = app.listen(app.get('port'), function() {
app.emit('started');
console.log(
log(
'freeCodeCamp server listening on port %d in %s',
app.get('port'),
app.get('env')
);
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',
'isDataVisCert',
'isFullStackCert',
'isRespWebDesignCert',
'isFrontEndLibsCert',
'isJsAlgoDataStructCert',
'isApisMicroservicesCert',
'isInfosecQaCert',
'githubURL',
'sendMonthlyEmail',

View File

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

View File

@@ -1,6 +1,11 @@
{
"frontEndCert": "Front 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) {
const {
isFrontEndCert,
isDataVisCert,
isBackEndCert,
isFullStackCert
isFullStackCert,
isRespWebDesignCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
} = user;
return Observable.fromNodeCallback(user.pledge, user)()
@@ -25,9 +30,15 @@ export function completeCommitment$(user) {
if (
(isFrontEndCert && goal === commitGoals.frontEndCert) ||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
(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');
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",
"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, {
type,
message,
redirectTo
redirectTo,
status = 200
}) {
err[_handledError] = { type, message, redirectTo };
err[_handledError] = { type, message, redirectTo, status };
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
.flatMap(({ entities, result: { superBlocks } }) => {
const superBlock = entities.superBlock;
const block = entities.block[blockDashedName];
const challenge = entities.challenge[challengeDashedName];
return Observable.if(
@@ -226,6 +227,7 @@ export function getChallenge(
`/challenges/${block.dashedName}/${challenge.dashedName}` :
false,
entities: {
superBlock,
challenge: {
[challenge.dashedName]: mapChallengeToLang(challenge, lang)
}

View File

@@ -43,3 +43,13 @@ export function ifNotVerifiedRedirectToSettings(req, res, 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.
script.
$(document).ready(function() {
function disableMagicButton (isDisabled) {
if (isDisabled) {
$('#magic-btn')
.html('<span class="fa fa-circle-o-notch fa-spin fa-fw"></span>')
.prop('disabled', true);
} else {
$('#magic-btn')
.html('<span class="fa.fa-envelope">Get a magic link to sign in.</span>')
.prop('disabled', false);
}
$(document).ready(function() {
function disableMagicButton (isDisabled) {
if (isDisabled) {
$('#magic-btn')
.prop('disabled', true)
.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>');
} else {
$('#magic-btn')
.prop('disabled', true)
.html('<span style="color:#E0E0E0;">Did not get a link? Reload the page and resend again.</span>');
}
}
$('form').submit(function(event){
@@ -54,34 +53,53 @@ block content
disableMagicButton(true);
var $form = $(event.target);
$.ajax({
type : 'POST',
url : $form.attr('action'),
data : $form.serialize(),
dataType : 'json',
encode : true,
xhrFields : { withCredentials: true }
type : 'POST',
url : $form.attr('action'),
data : $form.serialize(),
dataType : 'json',
encode : true,
xhrFields : { withCredentials: true }
})
.fail(error => {
if (error.responseText){
var data = JSON.parse(error.responseText);
if(data.error && data.error.message)
var data = JSON.parse(error.responseText);
if(data.error && data.error.message) {
$('#flash-content').html(data.error.message);
$('#flash-board')
.removeClass('alert-success')
.addClass('alert-info')
.slideDown(400)
.delay(800)
.fadeIn();
disableMagicButton(false);
}
}
})
.done(data =>{
if(data && data.message){
.done(data => {
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-board')
.removeClass('alert-info')
.addClass('alert-success')
.fadeIn();
.removeClass('alert-info alert-success alert-danger')
.addClass(alertType)
.slideDown(400)
.delay(800)
.fadeIn();
disableMagicButton(false);
}
});
});
});
});

View File

@@ -9,10 +9,10 @@ block content
if (!user.isGithubCool)
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github')
i.fa.fa-github
| Link my GitHub to unlock my portfolio
| Link my GitHub to enable my public profile
.col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings')
| Update your settings
| Update my settings
.col-xs-12
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/signout')
| Sign me out of freeCodeCamp
@@ -56,6 +56,21 @@ block content
if isBackEndCert
.button-spacer
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)
.button-spacer
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

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