Normalise tests and introduce front end libs json
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
93debd8892
commit
3550921b84
@ -5,13 +5,15 @@ const { dasherize } = require('./utils');
|
||||
const { viewTypes } = require('./utils/challengeTypes');
|
||||
const { blockNameify } = require('./utils/blockNameify');
|
||||
|
||||
const classic = path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/classic/Show.js'
|
||||
);
|
||||
|
||||
const views = {
|
||||
// backend: BackEnd,
|
||||
classic: path.resolve(
|
||||
__dirname,
|
||||
'./src/templates/Challenges/classic/Show.js'
|
||||
),
|
||||
// modern: Modern,
|
||||
classic,
|
||||
modern: classic,
|
||||
project: path.resolve(__dirname, './src/templates/Challenges/project/Show.js')
|
||||
// quiz: Quiz,
|
||||
// simple: Project,
|
||||
@ -46,6 +48,12 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
||||
node {
|
||||
challengeType
|
||||
id
|
||||
required {
|
||||
link
|
||||
raw
|
||||
src
|
||||
}
|
||||
template
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
@ -61,7 +69,7 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
||||
|
||||
// Create challenge pages.
|
||||
result.data.allChallengeNode.edges.forEach((edge, index, thisArray) => {
|
||||
const { fields: { slug }, challengeType, id } = edge.node;
|
||||
const { fields: { slug }, required = [], template, challengeType, id } = edge.node;
|
||||
const next = thisArray[index + 1];
|
||||
const nextChallengePath = next ? next.node.fields.slug : '/';
|
||||
createPage({
|
||||
@ -69,6 +77,8 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
||||
component: views[viewTypes[challengeType]],
|
||||
context: {
|
||||
challengeMeta: {
|
||||
template,
|
||||
required,
|
||||
nextChallengePath,
|
||||
id
|
||||
},
|
||||
|
@ -29,7 +29,7 @@
|
||||
"translations": {},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-alt-text-to-an-image-for-accessibility",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -70,7 +70,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -125,7 +125,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -178,7 +178,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -225,7 +225,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -292,7 +292,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -362,7 +362,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -437,7 +437,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -532,7 +532,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -598,7 +598,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -679,7 +679,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -763,7 +763,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -854,7 +854,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -923,7 +923,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1006,7 +1006,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1127,7 +1127,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1184,7 +1184,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1236,7 +1236,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1289,7 +1289,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1345,7 +1345,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1406,7 +1406,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1502,7 +1502,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -34,7 +34,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -100,7 +100,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -167,7 +167,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -242,7 +242,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -321,7 +321,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -395,7 +395,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -473,7 +473,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -548,7 +548,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -631,7 +631,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -704,7 +704,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -784,7 +784,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -862,7 +862,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -945,7 +945,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1043,7 +1043,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1110,7 +1110,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1174,7 +1174,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1214,7 +1214,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1261,7 +1261,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1310,7 +1310,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1358,7 +1358,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1413,7 +1413,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1474,7 +1474,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1542,7 +1542,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1603,7 +1603,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1659,7 +1659,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1709,7 +1709,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1771,7 +1771,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1842,7 +1842,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1937,7 +1937,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1996,7 +1996,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2074,7 +2074,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2144,7 +2144,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2197,7 +2197,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2235,7 +2235,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2297,7 +2297,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2350,7 +2350,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2399,7 +2399,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2460,7 +2460,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2528,7 +2528,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2622,7 +2622,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2680,7 +2680,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2730,7 +2730,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2793,7 +2793,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2861,7 +2861,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2923,7 +2923,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2993,7 +2993,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3097,7 +3097,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3190,7 +3190,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3280,7 +3280,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3359,7 +3359,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3441,7 +3441,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3509,7 +3509,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -23,8 +23,8 @@
|
||||
"testString": "assert($(\"h2\").css(\"color\") === \"rgb(255, 0, 0)\", 'Your <code>h2</code> element should be red.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>style</code> declaration should end with a <code>;</code>.",
|
||||
"testString": "assert(code.match(/<h2\\s+style\\s*=\\s*(\\'|\")\\s*color\\s*:\\s*(?:rgb\\(\\s*255\\s*,\\s*0\\s*,\\s*0\\s*\\)|rgb\\(\\s*100%\\s*,\\s*0%\\s*,\\s*0%\\s*\\)|red|#ff0000|#f00|hsl\\(\\s*0\\s*,\\s*100%\\s*,\\s*50%\\s*\\))\\s*\\;(\\'|\")>\\s*CatPhotoApp\\s*<\\/h2>/), 'Your <code>style</code> declaration should end with a <code>;</code>.');"
|
||||
"text": "Your <code>style</code> declaration should end with a <code>;</code> .",
|
||||
"testString": "assert(code.match(/<h2\\s+style\\s*=\\s*(\\'|\")\\s*color\\s*:\\s*(?:rgb\\(\\s*255\\s*,\\s*0\\s*,\\s*0\\s*\\)|rgb\\(\\s*100%\\s*,\\s*0%\\s*,\\s*0%\\s*\\)|red|#ff0000|#f00|hsl\\(\\s*0\\s*,\\s*100%\\s*,\\s*50%\\s*\\))\\s*\\;(\\'|\")>\\s*CatPhotoApp\\s*<\\/h2>/),' Your <code>style</code> declaration should end with a <code>;</code> .');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -79,7 +79,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -223,7 +223,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -366,7 +366,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -496,7 +496,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -601,7 +601,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -711,7 +711,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -846,7 +846,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -981,7 +981,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1111,7 +1111,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1262,7 +1262,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-borders-around-your-elements",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1385,7 +1385,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-rounded-corners-a-border-radius",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1508,7 +1508,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1644,7 +1644,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1784,7 +1784,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1938,7 +1938,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2087,7 +2087,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/adjust-the-padding-of-an-element",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2196,7 +2196,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/adjust-the-margin-of-an-element",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2307,7 +2307,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-a-negative-margin-to-an-element",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2424,7 +2424,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-different-padding-to-each-side-of-an-element",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2541,7 +2541,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2667,7 +2667,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2791,7 +2791,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2851,15 +2851,15 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>type</code> attribute selector should be used to select the checkboxes.",
|
||||
"testString": "assert(code.match(/<style>[\\s\\S]*?\\[type=(\"|')checkbox\\1\\]\\s*?{[\\s\\S]*?}[\\s\\S]*?<\\/style>/gi), 'The <code>type</code> attribute selector should be used to select the checkboxes.');"
|
||||
"testString": "assert(code.match(/<style>[\\s\\S]*?\\[type=(\"|')checkbox\\1\\]\\s*?{[\\s\\S]*?}[\\s\\S]*?<\\/style>/gi),'The <code>type</code> attribute selector should be used to select the checkboxes.');"
|
||||
},
|
||||
{
|
||||
"text": "The top margins of the checkboxes should be 10px.",
|
||||
"testString": "assert((function() {var count=0; $(\"[type='checkbox']\").each(function() { if($(this).css('marginTop') === '10px') {count++;}});return (count===3)}()), 'The top margins of the checkboxes should be 10px.');"
|
||||
"testString": "assert((function() {var count=0; $(\"[type='checkbox']\").each(function() { if($(this).css('marginTop') === '10px') {count++;}});return (count===3)}()),'The top margins of the checkboxes should be 10px.');"
|
||||
},
|
||||
{
|
||||
"text": "The bottom margins of the checkboxes should be 15px.",
|
||||
"testString": "assert((function() {var count=0; $(\"[type='checkbox']\").each(function() { if($(this).css('marginBottom') === '15px') {count++;}});return (count===3)}()), 'The bottom margins of the checkboxes should be 15px.');"
|
||||
"testString": "assert((function() {var count=0; $(\"[type='checkbox']\").each(function() { if($(this).css('marginBottom') === '15px') {count++;}});return (count===3)}()),'The bottom margins of the checkboxes should be 15px.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
@ -2869,7 +2869,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2967,7 +2967,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3089,7 +3089,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3193,7 +3193,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3282,7 +3282,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3394,7 +3394,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3524,7 +3524,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3631,7 +3631,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3755,7 +3755,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3849,7 +3849,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -3953,7 +3953,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -4076,7 +4076,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -4196,7 +4196,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -4292,7 +4292,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -4355,7 +4355,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -4590,7 +4590,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -4834,7 +4834,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -5084,7 +5084,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -5323,7 +5323,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -5558,7 +5558,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -5799,7 +5799,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -101,7 +101,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -189,7 +189,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -269,7 +269,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -342,7 +342,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -430,7 +430,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -519,7 +519,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -600,7 +600,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -675,7 +675,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -783,7 +783,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-images-to-your-website",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -879,7 +879,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -961,7 +961,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1089,7 +1089,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1167,7 +1167,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1263,7 +1263,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1366,7 +1366,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1489,7 +1489,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1586,7 +1586,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1688,7 +1688,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-placeholder-text-to-a-text-field",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1788,7 +1788,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1893,7 +1893,7 @@
|
||||
},
|
||||
"guideUrl": "https://guide.freecodecamp.org/certificates/add-a-submit-button-to-a-form",
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1991,7 +1991,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2127,7 +2127,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2249,7 +2249,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2350,7 +2350,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2467,7 +2467,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2554,7 +2554,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -2621,7 +2621,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -26,7 +26,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -107,7 +107,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -230,7 +230,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -289,7 +289,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -412,7 +412,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -467,7 +467,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -592,7 +592,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -648,7 +648,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -774,7 +774,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -838,7 +838,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -964,7 +964,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1050,7 +1050,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1111,7 +1111,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1179,7 +1179,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1245,7 +1245,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1303,7 +1303,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1364,7 +1364,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -26,7 +26,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -86,7 +86,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -144,7 +144,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -209,7 +209,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -271,7 +271,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -332,7 +332,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -393,7 +393,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -458,7 +458,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -523,7 +523,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -592,7 +592,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -658,7 +658,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -724,7 +724,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -786,7 +786,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -852,7 +852,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -919,7 +919,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -992,7 +992,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1065,7 +1065,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1129,7 +1129,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1195,7 +1195,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1275,7 +1275,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1356,7 +1356,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -1461,7 +1461,7 @@
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 1, 2016",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
@ -29,16 +30,6 @@
|
||||
"ru": {
|
||||
"title": "Создайте страницу посвященную тому что вас вдохновляет"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -52,21 +43,12 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 15, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
"translations": {}
|
||||
},
|
||||
{
|
||||
"id": "587d78af367417b2b2512b04",
|
||||
@ -79,21 +61,12 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 15, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
"translations": {}
|
||||
},
|
||||
{
|
||||
"id": "587d78b0367417b2b2512b05",
|
||||
@ -106,21 +79,12 @@
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 15, 2017",
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
"translations": {}
|
||||
},
|
||||
{
|
||||
"id": "bd7158d8c242eddfaeb5bd13",
|
||||
@ -132,6 +96,7 @@
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"challengeSeed": [],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
@ -146,16 +111,6 @@
|
||||
"ru": {
|
||||
"title": "Создайте сайт-портфолио"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -48,7 +48,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -115,7 +115,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -169,7 +169,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
@ -223,7 +223,7 @@
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"indexhtml": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
|
@ -490,7 +490,7 @@
|
||||
},
|
||||
{
|
||||
"text": "Only subtract one number from 45.",
|
||||
"testString": "assert(/var\\s*difference\\s*=\\s*45\\s*-\\s*[0-9]*;(?!\\s*[a-zA-Z0-9]+)/.test(code), 'Only subtract one number from 45.');"
|
||||
"testString": "assert(/var\\s*difference\\s*=\\s*45\\s*-\\s*[0-9]*;(?!\\s*[a-zA-Z0-9]+)/.test(code),'Only subtract one number from 45.');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -539,7 +539,7 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "Make the variable <code>product</code> equal 80",
|
||||
"testString": "assert(product === 80, 'Make the variable <code>product</code> equal 80');"
|
||||
"testString": "assert(product === 80,'Make the variable <code>product</code> equal 80');"
|
||||
},
|
||||
{
|
||||
"text": "Use the <code>*</code> operator",
|
||||
@ -1411,8 +1411,8 @@
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Remove all the <code>backslashes</code> (<code>\\</code>",
|
||||
"testString": "assert(!/\\\\/g.test(code) && myStr.match('\\\\s*<a href\\\\s*=\\\\s*\"http://www.example.com\"\\\\s*target\\\\s*=\\\\s*\"_blank\">\\\\s*Link\\\\s*</a>\\\\s*'), 'Remove all the <code>backslashes</code> (<code>\\</code>');"
|
||||
"text": "Remove all the <code>backslashes</code> (<code>\\</code>)",
|
||||
"testString": "assert(!/\\\\/g.test(code) && myStr.match('\\\\s*<a href\\\\s*=\\\\s*\"http://www.example.com\"\\\\s*target\\\\s*=\\\\s*\"_blank\">\\\\s*Link\\\\s*</a>\\\\s*'), 'Remove all the <code>backslashes</code> (<code>\\</code>)');"
|
||||
},
|
||||
{
|
||||
"text": "You should have two single quotes <code>'</code> and four double quotes <code>"</code>",
|
||||
@ -1473,8 +1473,8 @@
|
||||
"testString": "assert(!/ /.test(myStr), '<code>myStr</code> should not contain any spaces');"
|
||||
},
|
||||
{
|
||||
"text": "<code>myStr</code> should contain the strings <code>FirstLine</code>, <code>SecondLine</code> and <code>ThirdLine</code> (remember case sensitivity",
|
||||
"testString": "assert(/FirstLine/.test(myStr) && /SecondLine/.test(myStr) && /ThirdLine/.test(myStr), '<code>myStr</code> should contain the strings <code>FirstLine</code>, <code>SecondLine</code> and <code>ThirdLine</code> (remember case sensitivity');"
|
||||
"text": "<code>myStr</code> should contain the strings <code>FirstLine</code>, <code>SecondLine</code> and <code>ThirdLine</code> (remember case sensitivity)",
|
||||
"testString": "assert(/FirstLine/.test(myStr) && /SecondLine/.test(myStr) && /ThirdLine/.test(myStr), '<code>myStr</code> should contain the strings <code>FirstLine</code>, <code>SecondLine</code> and <code>ThirdLine</code> (remember case sensitivity)');"
|
||||
},
|
||||
{
|
||||
"text": "<code>FirstLine</code> should be followed by the newline character <code>\\n</code>",
|
||||
@ -2151,11 +2151,11 @@
|
||||
},
|
||||
{
|
||||
"text": "<code>wordBlanks(\"dog\", \"big\", \"ran\", \"quickly\")</code> should contain all of the passed in words separated by non-word characters (and any additional words in your madlib).",
|
||||
"testString": "assert(/\\bdog\\b/.test(test1) && /\\bbig\\b/.test(test1) && /\\bran\\b/.test(test1) && /\\bquickly\\b/.test(test1), '<code>wordBlanks(\"dog\", \"big\", \"ran\", \"quickly\")</code> should contain all of the passed in words separated by non-word characters (and any additional words in your madlib).');"
|
||||
"testString": "assert(/\\bdog\\b/.test(test1) && /\\bbig\\b/.test(test1) && /\\bran\\b/.test(test1) && /\\bquickly\\b/.test(test1),'<code>wordBlanks(\"dog\", \"big\", \"ran\", \"quickly\")</code> should contain all of the passed in words separated by non-word characters (and any additional words in your madlib).');"
|
||||
},
|
||||
{
|
||||
"text": "<code>wordBlanks(\"cat\", \"little\", \"hit\", \"slowly\")</code> should contain all of the passed in words separated by non-word characters (and any additional words in your madlib).",
|
||||
"testString": "assert(/\\bcat\\b/.test(test2) && /\\blittle\\b/.test(test2) && /\\bhit\\b/.test(test2) && /\\bslowly\\b/.test(test2), '<code>wordBlanks(\"cat\", \"little\", \"hit\", \"slowly\")</code> should contain all of the passed in words separated by non-word characters (and any additional words in your madlib).');"
|
||||
"testString": "assert(/\\bcat\\b/.test(test2) && /\\blittle\\b/.test(test2) && /\\bhit\\b/.test(test2) && /\\bslowly\\b/.test(test2),'<code>wordBlanks(\"cat\", \"little\", \"hit\", \"slowly\")</code> should contain all of the passed in words separated by non-word characters (and any additional words in your madlib).');"
|
||||
}
|
||||
],
|
||||
"type": "checkpoint",
|
||||
@ -5238,12 +5238,12 @@
|
||||
"testString": "assert(chainToSwitch(7) === \"Ate Nine\", '<code>chainToSwitch(7)</code> should be \"Ate Nine\"');"
|
||||
},
|
||||
{
|
||||
"text": "<code>chainToSwitch(\"John\")</code> should be \"\" (empty string",
|
||||
"testString": "assert(chainToSwitch(\"John\") === \"\", '<code>chainToSwitch(\"John\")</code> should be \"\" (empty string');"
|
||||
"text": "<code>chainToSwitch(\"John\")</code> should be \"\" (empty string)",
|
||||
"testString": "assert(chainToSwitch(\"John\") === \"\", '<code>chainToSwitch(\"John\")</code> should be \"\" (empty string)');"
|
||||
},
|
||||
{
|
||||
"text": "<code>chainToSwitch(156)</code> should be \"\" (empty string",
|
||||
"testString": "assert(chainToSwitch(156) === \"\", '<code>chainToSwitch(156)</code> should be \"\" (empty string');"
|
||||
"text": "<code>chainToSwitch(156)</code> should be \"\" (empty string)",
|
||||
"testString": "assert(chainToSwitch(156) === \"\", '<code>chainToSwitch(156)</code> should be \"\" (empty string)');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -5806,15 +5806,15 @@
|
||||
},
|
||||
{
|
||||
"text": "You should use bracket notation to access <code>testObj</code>",
|
||||
"testString": "assert(/testObj\\s*?\\[.*?\\]/.test(code), 'You should use bracket notation to access <code>testObj</code>');"
|
||||
"testString": "assert(/testObj\\s*?\\[.*?\\]/.test(code),'You should use bracket notation to access <code>testObj</code>');"
|
||||
},
|
||||
{
|
||||
"text": "You should not assign the value <code>Montana</code> to the variable <code>player</code> directly.",
|
||||
"testString": "assert(!code.match(/player\\s*=\\s*\"|\\'\\s*Montana\\s*\"|\\'\\s*;/gi), 'You should not assign the value <code>Montana</code> to the variable <code>player</code> directly.');"
|
||||
"testString": "assert(!code.match(/player\\s*=\\s*\"|\\'\\s*Montana\\s*\"|\\'\\s*;/gi),'You should not assign the value <code>Montana</code> to the variable <code>player</code> directly.');"
|
||||
},
|
||||
{
|
||||
"text": "You should be using the variable <code>playerNumber</code> in your bracket notation",
|
||||
"testString": "assert(/testObj\\s*?\\[\\s*playerNumber\\s*\\]/.test(code), 'You should be using the variable <code>playerNumber</code> in your bracket notation');"
|
||||
"testString": "assert(/testObj\\s*?\\[\\s*playerNumber\\s*\\]/.test(code),'You should be using the variable <code>playerNumber</code> in your bracket notation');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
|
@ -26,7 +26,7 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>var</code> does not exist in code.",
|
||||
"testString": "getUserInput => assert(!getUserInput('index').match(/var/g), '<code>var</code> does not exist in code.');"
|
||||
"testString": "getUserInput => assert(!getUserInput('index').match(/var/g),'<code>var</code> does not exist in code.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>catName</code> should be <code>Oliver</code>.",
|
||||
@ -85,7 +85,7 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>var</code> does not exist in code.",
|
||||
"testString": "getUserInput => assert(!getUserInput('index').match(/var/g), '<code>var</code> does not exist in code.');"
|
||||
"testString": "getUserInput => assert(!getUserInput('index').match(/var/g),'<code>var</code> does not exist in code.');"
|
||||
},
|
||||
{
|
||||
"text": "The variable <code>i</code> declared in the if statement should equal \"block scope\".",
|
||||
@ -137,7 +137,7 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>var</code> does not exist in code.",
|
||||
"testString": "getUserInput => assert(!getUserInput('index').match(/var/g), '<code>var</code> does not exist in code.');"
|
||||
"testString": "getUserInput => assert(!getUserInput('index').match(/var/g),'<code>var</code> does not exist in code.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>SENTENCE</code> should be a constant variable (by using <code>const</code>).",
|
||||
@ -606,11 +606,11 @@
|
||||
},
|
||||
{
|
||||
"text": "<code>...</code> spread operator was used to duplicate <code>arr1</code>.",
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\[\\s*...arr1\\s*\\]/g), '<code>...</code> spread operator was used to duplicate <code>arr1</code>.');"
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\[\\s*...arr1\\s*\\]/g),'<code>...</code> spread operator was used to duplicate <code>arr1</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>arr2</code> remains unchanged when <code>arr1</code> is changed.",
|
||||
"testString": "assert((arr1, arr2) => {arr1.push('JUN'); return arr2.length < arr1.length}, '<code>arr2</code> remains unchanged when <code>arr1</code> is changed.');"
|
||||
"testString": "assert((arr1, arr2) => {arr1.push('JUN'); return arr2.length < arr1.length},'<code>arr2</code> remains unchanged when <code>arr1</code> is changed.');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -663,7 +663,7 @@
|
||||
},
|
||||
{
|
||||
"text": "destructuring with reassignment was used",
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\{\\s*length\\s*:\\s*len\\s*}\\s*=\\s*str/g), 'destructuring with reassignment was used');"
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\{\\s*length\\s*:\\s*len\\s*}\\s*=\\s*str/g),'destructuring with reassignment was used');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -712,7 +712,7 @@
|
||||
},
|
||||
{
|
||||
"text": "nested destructuring was used",
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\{\\s*tomorrow\\s*:\\s*\\{\\s*max\\s*:\\s*maxOfTomorrow\\s*\\}\\s*\\}\\s*=\\s*forecast/g), 'nested destructuring was used');"
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\{\\s*tomorrow\\s*:\\s*\\{\\s*max\\s*:\\s*maxOfTomorrow\\s*\\}\\s*\\}\\s*=\\s*forecast/g),'nested destructuring was used');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -813,11 +813,11 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>arr</code> should be <code>[3,4,5,6,7,8,9,10]</code>",
|
||||
"testString": "assert(arr.every((v, i) => v === i + 3), '<code>arr</code> should be <code>[3,4,5,6,7,8,9,10]</code>');"
|
||||
"testString": "assert(arr.every((v, i) => v === i + 3),'<code>arr</code> should be <code>[3,4,5,6,7,8,9,10]</code>');"
|
||||
},
|
||||
{
|
||||
"text": "destructuring was used.",
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\[\\s*\\w\\s*,\\s*\\w\\s*,\\s*...arr\\s*\\]/g), 'destructuring was used.');"
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/\\[\\s*\\w\\s*,\\s*\\w\\s*,\\s*...arr\\s*\\]/g),'destructuring was used.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>Array.slice()</code> was not used.",
|
||||
@ -1102,11 +1102,11 @@
|
||||
},
|
||||
{
|
||||
"text": "<code>class</code> keyword was used.",
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/class/g), '<code>class</code> keyword was used.');"
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/class/g),'<code>class</code> keyword was used.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>Vegetable</code> can be instantiated.",
|
||||
"testString": "assert(() => {const a = new Vegetable(\"apple\"); return typeof a === 'object';}, '<code>Vegetable</code> can be instantiated.');"
|
||||
"testString": "assert(() => {const a = new Vegetable(\"apple\"); return typeof a === 'object';},'<code>Vegetable</code> can be instantiated.');"
|
||||
}
|
||||
],
|
||||
"type": "waypoint",
|
||||
@ -1158,11 +1158,11 @@
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>Thermostat</code> should be a <code>class</code> with a defined <code>constructor</code> method.",
|
||||
"testString": "assert(typeof Thermostat === 'function' && typeof Thermostat.constructor === 'function', '<code>Thermostat</code> should be a <code>class</code> with a defined <code>constructor</code> method.');"
|
||||
"testString": "assert(typeof Thermostat === 'function' && typeof Thermostat.constructor === 'function','<code>Thermostat</code> should be a <code>class</code> with a defined <code>constructor</code> method.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>class</code> keyword was used.",
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/class/g), '<code>class</code> keyword was used.');"
|
||||
"testString": "getUserInput => assert(getUserInput('index').match(/class/g),'<code>class</code> keyword was used.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>Thermostat</code> can be instantiated.",
|
||||
|
@ -818,8 +818,8 @@
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should show that <code>Object.prototype</code> is the prototype of <code>Dog.prototype</code>\"",
|
||||
"testString": "assert(/Object\\.prototype\\.isPrototypeOf/.test(code), 'Your code should show that <code>Object.prototype</code> is the prototype of <code>Dog.prototype</code>\"');"
|
||||
"text": "Your code should show that <code>Object.prototype</code> is the prototype of <code>Dog.prototype</code>\")",
|
||||
"testString": "assert(/Object\\.prototype\\.isPrototypeOf/.test(code), \"Your code should show that <code>Object.prototype</code> is the prototype of <code>Dog.prototype</code>\");"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
|
3131
packages/learn/seed/challenges/03-front-end-libraries/bootstrap.json
Normal file
3131
packages/learn/seed/challenges/03-front-end-libraries/bootstrap.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,127 @@
|
||||
{
|
||||
"name": "Front End Libraries Projects",
|
||||
"order": 8,
|
||||
"time": "150 hours",
|
||||
"helpRoom": "Help",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "bd7158d8c442eddfaeb5bd13",
|
||||
"title": "Build a Random Quote Machine",
|
||||
"description": [
|
||||
"Fulfill the user stories by getting all of the tests to pass. Use whichever libraries you need. Give it your own personal style.",
|
||||
"Here's a <a href='http://codepen.io/freeCodeCamp/full/qRZeGZ' target='_blank'>working example</a>. Try not to look at its code.",
|
||||
"You can build your project by forking <a href='http://codepen.io/freeCodeCamp/pen/MJjpwO' target='_blank'>this CodePen pen</a>. Or you can use this CDN link to run the tests in any environment you like: <code>https://gitcdn.link/repo/freeCodeCamp/testable-projects-fcc/master/build/bundle.js</code>",
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Crea una máquina de frases aleatorias"
|
||||
},
|
||||
"ru": {
|
||||
"title": "Создайте генератор случайных цитат"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bd7157d8c242eddfaeb5bd13",
|
||||
"title": "Build a Markdown Previewer",
|
||||
"description": [
|
||||
"Fulfill the user stories by getting all of the tests to pass. Use whichever libraries you need. Give it your own personal style.",
|
||||
"Here's a <a href='http://codepen.io/freeCodeCamp/full/GrZVVO' target='_blank'>working example</a>. Try not to look at its code.",
|
||||
"You can build your project by forking <a href='http://codepen.io/freeCodeCamp/pen/MJjpwO' target='_blank'>this CodePen pen</a>. Or you can use this CDN link to run the tests in any environment you like: <code>https://gitcdn.link/repo/freeCodeCamp/testable-projects-fcc/master/build/bundle.js</code>",
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "January 10, 2017",
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Crea una caja de recetas"
|
||||
},
|
||||
"ru": {
|
||||
"title": "Создайте хранилище рецептов"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbc367417b2b2512bae",
|
||||
"title": "Build a Drum Machine",
|
||||
"description": [
|
||||
"Fulfill the user stories by getting all of the tests to pass. Use whichever libraries you need. Give it your own personal style.",
|
||||
"Here's a <a href='http://codepen.io/freeCodeCamp/full/MJyNMd' target='_blank'>working example</a>. Try not to look at its code.",
|
||||
"You can build your project by forking <a href='http://codepen.io/freeCodeCamp/pen/MJjpwO' target='_blank'>this CodePen pen</a>. Or you can use this CDN link to run the tests in any environment you like: <code>https://gitcdn.link/repo/freeCodeCamp/testable-projects-fcc/master/build/bundle.js</code>",
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"releasedOn": "February 17, 2017",
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Crea un Juego de la vida"
|
||||
},
|
||||
"ru": {
|
||||
"title": "Создайте игру \"Жизнь\""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bd7158d8c442eddfaeb5bd17",
|
||||
"title": "Build a JavaScript Calculator",
|
||||
"description": [
|
||||
"Fulfill the user stories by getting all of the tests to pass. Use whichever libraries you need. Give it your own personal style.",
|
||||
"Here's a <a href='http://codepen.io/freeCodeCamp/full/wgGVVX' target='_blank'>working example</a>. Try not to look at its code.",
|
||||
"<strong>Note On Calculator Logic:</strong> It should be noted that there are two main schools of thought on calculator input logic: <dfn>immediate execution logic</dfn> and <dfn>formula logic</dfn>. Our example utilizes formula logic and observes order of operation precedence, immediate execution does not. Either is acceptable, but please note that depending on which you choose, your calculator may yield different results than ours for certain equations (see below example). As long as your math can be verified by another production calculator, please do not consider this a bug.",
|
||||
"<strong>EXAMPLE:</strong> <code>3 + 5 x 6 - 2 / 4 =</code><br><ul><li><strong>Immediate Execution Logic:</strong> <code>11.5</code></li><li><strong>Formula/Expression Logic:</strong> <code>32.5</code></li></ul>",
|
||||
"You can build your project by forking <a href='http://codepen.io/freeCodeCamp/pen/MJjpwO' target='_blank'>this CodePen pen</a>. Or you can use this CDN link to run the tests in any environment you like: <code>https://gitcdn.link/repo/freeCodeCamp/testable-projects-fcc/master/build/bundle.js</code>",
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"challengeType": 3,
|
||||
"isRequired": true,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Crea una calculadora JavaScript"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bd7158d8c442eddfaeb5bd0f",
|
||||
"title": "Build a Pomodoro Clock",
|
||||
"description": [
|
||||
"Fulfill the user stories by getting all of the tests to pass. Use whichever libraries you need. Give it your own personal style.",
|
||||
"Here's a <a href='http://codepen.io/freeCodeCamp/full/XpKrrW' target='_blank'>working example</a>. Try not to look at its code.",
|
||||
"You can build your project by forking <a href='http://codepen.io/freeCodeCamp/pen/MJjpwO' target='_blank'>this CodePen pen</a>. Or you can use this CDN link to run the tests in any environment you like: <code>https://gitcdn.link/repo/freeCodeCamp/testable-projects-fcc/master/build/bundle.js</code>",
|
||||
"Once you're done, submit the URL to your working project with all its tests passing.",
|
||||
"Remember to use the <a href='https://forum.freecodecamp.org/t/how-to-get-help-when-you-are-stuck/19514' target='_blank'>Read-Search-Ask</a> method if you get stuck."
|
||||
],
|
||||
"tests": [],
|
||||
"type": "zipline",
|
||||
"isRequired": true,
|
||||
"challengeType": 3,
|
||||
"translations": {
|
||||
"es": {
|
||||
"title": "Crea un reloj pomodoro"
|
||||
},
|
||||
"ru": {
|
||||
"title": "Создайте таймер Pomodoro"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fileName": "03-front-end-libraries/front-end-libraries-projects.json",
|
||||
"superBlock": "front-end-libraries",
|
||||
"superOrder": 3
|
||||
}
|
1865
packages/learn/seed/challenges/03-front-end-libraries/jquery.json
Normal file
1865
packages/learn/seed/challenges/03-front-end-libraries/jquery.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,896 @@
|
||||
{
|
||||
"name": "React and Redux",
|
||||
"order": 7,
|
||||
"time": "5 hours",
|
||||
"helpRoom": "Help",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/react/16.1.1/umd/react.development.js"
|
||||
},
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.1.1/umd/react-dom.development.js"
|
||||
},
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"
|
||||
},
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js"
|
||||
}
|
||||
],
|
||||
"template": "<body><div id='root'></div>${ source }</body>",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "5a24c314108439a4d4036141",
|
||||
"title": "Getting Started with React Redux",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"This series of challenges introduces how to use Redux with React. First, here's a review of some of the key principles of each technology. React is a view library that you provide with data, then it renders the view in an efficient, predictable way. Redux is a state management framework that you can use to simplify the management of your application's state. Typically, in a React Redux app, you create a single Redux store that manages the state of your entire app. Your React components subscribe to only the pieces of data in the store that are relevant to their role. Then, you dispatch actions directly from React components, which then trigger store updates.",
|
||||
"Although React components can manage their own state locally, when you have a complex app, it's generally better to keep the app state in a single location with Redux. There are exceptions when individual components may have local state specific only to them. Finally, because Redux is not designed to work with React out of the box, you need to use the <code>react-redux</code> package. It provides a way for you to pass Redux <code>state</code> and <code>dispatch</code> to your React components as <code>props</code>.",
|
||||
"Over the next few challenges, first, you'll create a simple React component which allows you to input new text messages. These are added to an array that's displayed in the view. This should be a nice review of what you learned in the React lessons. Next, you'll create a Redux store and actions that manage the state of the messages array. Finally, you'll use <code>react-redux</code> to connect the Redux store with your component, thereby extracting the local state into the Redux store.",
|
||||
"<hr>",
|
||||
"Start with a <code>DisplayMessages</code> component. Add a constructor to this component and initialize it with a state that has two properties: <code>input</code>, that's set to an empty string, and <code>messages</code>, that's set to an empty array."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"class DisplayMessages extends React.Component {",
|
||||
" // change code below this line",
|
||||
"",
|
||||
" // change code above this line",
|
||||
" render() {",
|
||||
" return <div />",
|
||||
" }",
|
||||
"};"
|
||||
],
|
||||
"tail": "ReactDOM.render(<DisplayMessages />, document.getElementById('root'))"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>DisplayMessages</code> component should render an empty <code>div</code> element.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })(), 'The <code>DisplayMessages</code> component should render an empty <code>div</code> element.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.",
|
||||
"testString": "getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>DisplayMessages</code> component should have an initial state equal to <code>{input: \"\", messages: []}</code>.",
|
||||
"testString": "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; })(), 'The <code>DisplayMessages</code> component should have an initial state equal to <code>{input: \"\", messages: []}</code>.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"class DisplayMessages extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n }\n render() {\n return <div/>\n }\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"react": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036142",
|
||||
"title": "Manage State Locally First",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"Here you'll finish creating the <code>DisplayMessages</code> component.",
|
||||
"<hr>",
|
||||
"First, in the <code>render()</code> method, have the component render an <code>input</code> element, <code>button</code> element, and <code>ul</code> element. When the <code>input</code> element changes, it should trigger a <code>handleChange()</code> method. Also, the <code>input</code> element should render the value of <code>input</code> that's in the component's state. The <code>button</code> element should trigger a <code>submitMessage()</code> method when it's clicked.",
|
||||
"Second, write these two methods. The <code>handleChange()</code> method should update the <code>input</code> with what the user is typing. The <code>submitMessage()</code> method should concatenate the current message (stored in <code>input</code>) to the <code>messages</code> array in local state, and clear the value of the <code>input</code>.",
|
||||
"Finally, use the <code>ul</code> to map over the array of <code>messages</code> and render it to the screen as a list of <code>li</code> elements."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"class DisplayMessages extends React.Component {",
|
||||
" constructor(props) {",
|
||||
" super(props);",
|
||||
" this.state = {",
|
||||
" input: '',",
|
||||
" messages: []",
|
||||
" }",
|
||||
" }",
|
||||
" // add handleChange() and submitMessage() methods here",
|
||||
"",
|
||||
" render() {",
|
||||
" return (",
|
||||
" <div>",
|
||||
" <h2>Type in a new Message:</h2>",
|
||||
" { /* render an input, button, and ul here */ }",
|
||||
"",
|
||||
" { /* change code above this line */ }",
|
||||
" </div>",
|
||||
" );",
|
||||
" }",
|
||||
"};"
|
||||
],
|
||||
"tail": "ReactDOM.render(<DisplayMessages />, document.getElementById('root'))"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>DisplayMessages</code> component should initialize with a state equal to <code>{ input: \"\", messages: [] }</code>.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return ( typeof initialState === 'object' && initialState.input === '' && initialState.messages.length === 0); })(), 'The <code>DisplayMessages</code> component should initialize with a state equal to <code>{ input: \"\", messages: [] }</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>DisplayMessages</code> component should render a <code>div</code> containing an <code>h2</code> element, a <code>button</code> element, a <code>ul</code> element, and <code>li</code> elements as children.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const state = () => { mockedComponent.setState({messages: ['__TEST__MESSAGE']}); return waitForIt(() => mockedComponent )}; const updated = await state(); assert(updated.find('div').length === 1 && updated.find('h2').length === 1 && updated.find('button').length === 1 && updated.find('ul').length === 1, 'The <code>DisplayMessages</code> component should render a <code>div</code> containing an <code>h2</code> element, a <code>button</code> element, a <code>ul</code> element, and <code>li</code> elements as children.'); }; "
|
||||
},
|
||||
{
|
||||
"text": "The <code>input</code> element should render the value of <code>input</code> in local state.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const testValue = '__TEST__EVENT__INPUT'; const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); assert(updated.find('input').props().value === testValue, 'The <code>input</code> element should render the value of <code>input</code> in local state.'); }; "
|
||||
},
|
||||
{
|
||||
"text": "Calling the method <code>handleChange</code> should update the <code>input</code> value in state to the current input.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = '__TEST__EVENT__MESSAGE__'; const changed = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const afterInput = await changed(); assert(initialState.input === '' && afterInput.state().input === '__TEST__EVENT__MESSAGE__', 'Calling the method <code>handleChange</code> should update the <code>input</code> value in state to the current input.'); }; "
|
||||
},
|
||||
{
|
||||
"text": "Clicking the <code>Add message</code> button should call the method <code>submitMessage</code> which should add the current <code>input</code> to the <code>messages</code> array in state.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage_1 = '__FIRST__MESSAGE__'; const firstChange = () => { causeChange(mockedComponent, testMessage_1); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstSubmit = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterSubmit_1 = await firstSubmit(); const submitState_1 = afterSubmit_1.state(); const testMessage_2 = '__SECOND__MESSAGE__'; const secondChange = () => { causeChange(mockedComponent, testMessage_2); return waitForIt(() => mockedComponent )}; const secondResult = await secondChange(); const secondSubmit = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterSubmit_2 = await secondSubmit(); const submitState_2 = afterSubmit_2.state(); assert(initialState.messages.length === 0 && submitState_1.messages.length === 1 && submitState_2.messages.length === 2 && submitState_2.messages[1] === testMessage_2, 'Clicking the <code>Add message</code> button should call the method <code>submitMessage</code> which should add the current <code>input</code> to the <code>messages</code> array in state.'); }; "
|
||||
},
|
||||
{
|
||||
"text": "The <code>submitMessage</code> method should clear the current input.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = '__FIRST__MESSAGE__'; const firstChange = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstState = firstResult.state(); const firstSubmit = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterSubmit = await firstSubmit(); const submitState = afterSubmit.state(); assert(firstState.input === testMessage && submitState.input === '', 'The <code>submitMessage</code> method should clear the current input.'); }; "
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"class DisplayMessages extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n const currentMessage = this.state.input;\n this.setState({\n input: '',\n messages: this.state.messages.concat(currentMessage)\n });\n }\n render() {\n return (\n <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.state.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036143",
|
||||
"title": "Extract State Logic to Redux",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"Now that you finished the React component, you need to move the logic it's performing locally in its <code>state</code> into Redux. This is the first step to connect the simple React app to Redux. The only functionality your app has is to add new messages from the user to an unordered list. The example is simple in order to demonstrate how React and Redux work together.",
|
||||
"<hr>",
|
||||
"First, define an action type 'ADD' and set it to a const <code>ADD</code>. Next, define an action creator <code>addMessage()</code> which creates the action to add a message. You'll need to pass a <code>message</code> to this action creator and include the message in the returned <code>action</code>.",
|
||||
"Then create a reducer called <code>messageReducer()</code> that handles the state for the messages. The initial state should equal an empty array. This reducer should add a message to the array of messages held in state, or return the current state. Finally, create your Redux store and pass it the reducer."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// define ADD, addMessage(), messageReducer(), and store here:",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The const <code>ADD</code> should exist and hold a value equal to the string <code>ADD</code>",
|
||||
"testString": "assert(ADD === 'ADD', 'The const <code>ADD</code> should exist and hold a value equal to the string <code>ADD</code>');"
|
||||
},
|
||||
{
|
||||
"text": "The action creator <code>addMessage</code> should return an object with <code>type</code> equal to <code>ADD</code> and message equal to the message that is passed in.",
|
||||
"testString": "assert((function() { const addAction = addMessage('__TEST__MESSAGE__'); return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__'; })(), 'The action creator <code>addMessage</code> should return an object with <code>type</code> equal to <code>ADD</code> and message equal to the message that is passed in.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>messageReducer</code> should be a function.",
|
||||
"testString": "assert(typeof messageReducer === 'function', '<code>messageReducer</code> should be a function.');"
|
||||
},
|
||||
{
|
||||
"text": "The store should exist and have an initial state set to an empty array.",
|
||||
"testString": "assert((function() { const initialState = store.getState(); return typeof store === 'object' && initialState.length === 0; })(), 'The store should exist and have an initial state set to an empty array.');"
|
||||
},
|
||||
{
|
||||
"text": "Dispatching <code>addMessage</code> against the store should immutably add a new message to the array of messages held in state.",
|
||||
"testString": "assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addMessage('__A__TEST__MESSAGE')); const addState = store.getState(); return (isFrozen && addState[0] === '__A__TEST__MESSAGE'); })(), 'Dispatching <code>addMessage</code> against the store should immutably add a new message to the array of messages held in state.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>messageReducer</code> should return the current state if called with any other actions.",
|
||||
"testString": "assert((function() { const addState = store.getState(); store.dispatch({type: 'FAKE_ACTION'}); const testState = store.getState(); return (addState === testState); })(), 'The <code>messageReducer</code> should return the current state if called with any other actions.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"const ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036144",
|
||||
"title": "Use Provider to Connect Redux to React",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"In the last challenge, you created a Redux store to handle the messages array and created an action for adding new messages. The next step is to provide React access to the Redux store and the actions it needs to dispatch updates. React Redux provides its <code>react-redux</code> package to help accomplish these tasks.",
|
||||
"React Redux provides a small API with two key features: <code>Provider</code> and <code>connect</code>. Another challenge covers <code>connect</code>. The <code>Provider</code> is a wrapper component from React Redux that wraps your React app. This wrapper then allows you to access the Redux <code>store</code> and <code>dispatch</code> functions throughout your component tree. <code>Provider</code> takes two props, the Redux store and the child components of your app. Defining the <code>Provider</code> for an App component might look like this:",
|
||||
"<blockquote><Provider store={store}><br> <App/><br></Provider></blockquote>",
|
||||
"<hr>",
|
||||
"The code editor now shows all your Redux and React code from the past several challenges. It includes the Redux store, actions, and the <code>DisplayMessages</code> component. The only new piece is the <code>AppWrapper</code> component at the bottom. Use this top level component to render the <code>Provider</code> from <code>ReactRedux</code>, and pass the Redux store as a prop. Then render the <code>DisplayMessages</code> component as a child. Once you are finished, you should see your React component rendered to the page.",
|
||||
"<strong>Note:</strong> React Redux is available as a global variable here, so you can access the Provider with dot notation. The code in the editor takes advantage of this and sets it to a constant <code>Provider</code> for you to use in the <code>AppWrapper</code> render method."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// Redux Code:",
|
||||
"const ADD = 'ADD';",
|
||||
"",
|
||||
"const addMessage = (message) => {",
|
||||
" return {",
|
||||
" type: ADD,",
|
||||
" message",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const messageReducer = (state = [], action) => {",
|
||||
" switch (action.type) {",
|
||||
" case ADD:",
|
||||
" return [",
|
||||
" ...state,",
|
||||
" action.message",
|
||||
" ];",
|
||||
" default:",
|
||||
" return state;",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"const store = Redux.createStore(messageReducer);",
|
||||
"",
|
||||
"// React Code:",
|
||||
"",
|
||||
"class DisplayMessages extends React.Component {",
|
||||
" constructor(props) {",
|
||||
" super(props);",
|
||||
" this.state = {",
|
||||
" input: '',",
|
||||
" messages: []",
|
||||
" }",
|
||||
" this.handleChange = this.handleChange.bind(this);",
|
||||
" this.submitMessage = this.submitMessage.bind(this);",
|
||||
" }",
|
||||
" handleChange(event) {",
|
||||
" this.setState({",
|
||||
" input: event.target.value",
|
||||
" });",
|
||||
" }",
|
||||
" submitMessage() {",
|
||||
" const currentMessage = this.state.input;",
|
||||
" this.setState({",
|
||||
" input: '',",
|
||||
" messages: this.state.messages.concat(currentMessage)",
|
||||
" });",
|
||||
" }",
|
||||
" render() {",
|
||||
" return (",
|
||||
" <div>",
|
||||
" <h2>Type in a new Message:</h2>",
|
||||
" <input",
|
||||
" value={this.state.input}",
|
||||
" onChange={this.handleChange}/><br/>",
|
||||
" <button onClick={this.submitMessage}>Submit</button>",
|
||||
" <ul>",
|
||||
" {this.state.messages.map( (message, idx) => {",
|
||||
" return (",
|
||||
" <li key={idx}>{message}</li>",
|
||||
" )",
|
||||
" })",
|
||||
" }",
|
||||
" </ul>",
|
||||
" </div>",
|
||||
" );",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const Provider = ReactRedux.Provider;",
|
||||
"",
|
||||
"class AppWrapper extends React.Component {",
|
||||
" // render the Provider here",
|
||||
"",
|
||||
" // change code above this line",
|
||||
"};"
|
||||
],
|
||||
"tail": "ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>AppWrapper</code> should render.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'The <code>AppWrapper</code> should render.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Provider</code> wrapper component should have a prop of <code>store</code> passed to it, equal to the Redux store.",
|
||||
"testString": "getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\\s/g,'').includes('<Providerstore={store}>'); })(), 'The <code>Provider</code> wrapper component should have a prop of <code>store</code> passed to it, equal to the Redux store.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })(), '<code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>DisplayMessages</code> component should render an h2, input, button, and <code>ul</code> element.",
|
||||
"testString": "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; })(), 'The <code>DisplayMessages</code> component should render an h2, input, button, and <code>ul</code> element.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"// Redux Code:\nconst ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);\n\n// React Code:\n\nclass DisplayMessages extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n const currentMessage = this.state.input;\n this.setState({\n input: '',\n messages: this.state.messages.concat(currentMessage)\n });\n }\n render() {\n return (\n <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.state.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};\n\nconst Provider = ReactRedux.Provider;\n\nclass AppWrapper extends React.Component {\n // change code below this line\n render() {\n return (\n <Provider store = {store}>\n <DisplayMessages/>\n </Provider>\n );\n }\n // change code above this line\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036145",
|
||||
"title": "Map State to Props",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"The <code>Provider</code> component allows you to provide <code>state</code> and <code>dispatch</code> to your React components, but you must specify exactly what state and actions you want. This way, you make sure that each component only has access to the state it needs. You accomplish this by creating two functions: <code>mapStateToProps()</code> and <code>mapDispatchToProps()</code>.",
|
||||
"In these functions, you declare what pieces of state you want to have access to and which action creators you need to be able to dispatch. Once these functions are in place, you'll see how to use the React Redux <code>connect</code> method to connect them to your components in another challenge.",
|
||||
"<strong>Note:</strong> Behind the scenes, React Redux uses the <code>store.subscribe()</code> method to implement <code>mapStateToProps()</code>.",
|
||||
"<hr>",
|
||||
"Create a function <code>mapStateToProps()</code>. This function should take <code>state</code> as an argument, then return an object which maps that state to specific property names. These properties will become accessible to your component via <code>props</code>. Since this example keeps the entire state of the app in a single array, you can pass that entire state to your component. Create a property <code>messages</code> in the object that's being returned, and set it to <code>state</code>."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"const state = [];",
|
||||
"",
|
||||
"// change code below this line",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The const <code>state</code> should be an empty array.",
|
||||
"testString": "assert(Array.isArray(state) && state.length === 0, 'The const <code>state</code> should be an empty array.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>mapStateToProps</code> should be a function.",
|
||||
"testString": "assert(typeof mapStateToProps === 'function', '<code>mapStateToProps</code> should be a function.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>mapStateToProps</code> should return an object.",
|
||||
"testString": "assert(typeof mapStateToProps() === 'object', '<code>mapStateToProps</code> should return an object.');"
|
||||
},
|
||||
{
|
||||
"text": "Passing an array as state to <code>mapStateToProps</code> should return this array assigned to a key of <code>messages</code>.",
|
||||
"testString": "assert(mapStateToProps(['messages']).messages.pop() === 'messages', 'Passing an array as state to <code>mapStateToProps</code> should return this array assigned to a key of <code>messages</code>.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"const state = [];\n\n// change code below this line\n\nconst mapStateToProps = (state) => {\n return {\n messages: state\n }\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036146",
|
||||
"title": "Map Dispatch to Props",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"The <code>mapDispatchToProps()</code> function is used to provide specific action creators to your React components so they can dispatch actions against the Redux store. It's similar in structure to the <code>mapStateToProps()</code> function you wrote in the last challenge. It returns an object that maps dispatch actions to property names, which become component <code>props</code>. However, instead of returning a piece of <code>state</code>, each property returns a function that calls <code>dispatch</code> with an action creator and any relevant action data. You have access to this <code>dispatch</code> because it's passed in to <code>mapDispatchToProps()</code> as a parameter when you define the function, just like you passed <code>state</code> to <code>mapStateToProps()</code>. Behind the scenes, React Redux is using Redux's <code>store.dispatch()</code> to conduct these dispatches with <code>mapDispatchToProps()</code>. This is similar to how it uses <code>store.subscribe()</code> for components that are mapped to <code>state</code>.",
|
||||
"For example, you have a <code>loginUser()</code> action creator that takes a <code>username</code> as an action payload. The object returned from <code>mapDispatchToProps()</code> for this action creator would look something like:",
|
||||
"<blockquote>{<br> submitLoginUser: function(username) {<br> dispatch(loginUser(username));<br> }<br>}</blockquote>",
|
||||
"<hr>",
|
||||
"The code editor provides an action creator called <code>addMessage()</code>. Write the function <code>mapDispatchToProps()</code> that takes <code>dispatch</code> as an argument, then returns an object. The object should have a property <code>submitNewMessage</code> set to the dispatch function, which takes a parameter for the new message to add when it dispatches <code>addMessage()</code>."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"const addMessage = (message) => {",
|
||||
" return {",
|
||||
" type: 'ADD',",
|
||||
" message: message",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"// change code below this line",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "<code>addMessage</code> should return an object with keys <code>type</code> and <code>message</code>.",
|
||||
"testString": "assert((function() { const addMessageTest = addMessage(); return ( addMessageTest.hasOwnProperty('type') && addMessageTest.hasOwnProperty('message')); })(), '<code>addMessage</code> should return an object with keys <code>type</code> and <code>message</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>mapDispatchToProps</code> should be a function.",
|
||||
"testString": "assert(typeof mapDispatchToProps === 'function', '<code>mapDispatchToProps</code> should be a function.');"
|
||||
},
|
||||
{
|
||||
"text": "<code>mapDispatchToProps</code> should return an object.",
|
||||
"testString": "assert(typeof mapDispatchToProps() === 'object', '<code>mapDispatchToProps</code> should return an object.');"
|
||||
},
|
||||
{
|
||||
"text": "Dispatching <code>addMessage</code> with <code>submitNewMessage</code> from <code>mapDispatchToProps</code> should return a message to the dispatch function.",
|
||||
"testString": "assert((function() { let testAction; const dispatch = (fn) => { testAction = fn; }; let dispatchFn = mapDispatchToProps(dispatch); dispatchFn.submitNewMessage('__TEST__MESSAGE__'); return (testAction.type === 'ADD' && testAction.message === '__TEST__MESSAGE__'); })(), 'Dispatching <code>addMessage</code> with <code>submitNewMessage</code> from <code>mapDispatchToProps</code> should return a message to the dispatch function.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"const addMessage = (message) => {\n return {\n type: 'ADD',\n message: message\n }\n};\n\n// change code below this line\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: function(message) {\n dispatch(addMessage(message));\n }\n }\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036147",
|
||||
"title": "Connect Redux to React",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"Now that you've written both the <code>mapStateToProps()</code> and the <code>mapDispatchToProps()</code> functions, you can use them to map <code>state</code> and <code>dispatch</code> to the <code>props</code> of one of your React components. The <code>connect</code> method from React Redux can handle this task. This method takes two optional arguments, <code>mapStateToProps()</code> and <code>mapDispatchToProps()</code>. They are optional because you may have a component that only needs access to <code>state</code> but doesn't need to dispatch any actions, or vice versa.",
|
||||
"To use this method, pass in the functions as arguments, and immediately call the result with your component. This syntax is a little unusual and looks like:",
|
||||
"<code>connect(mapStateToProps, mapDispatchToProps)(MyComponent)</code>",
|
||||
"<strong>Note:</strong> If you want to omit one of the arguments to the <code>connect</code> method, you pass <code>null</code> in its place.",
|
||||
"<hr>",
|
||||
"The code editor has the <code>mapStateToProps()</code> and <code>mapDispatchToProps()</code> functions and a new React component called <code>Presentational</code>. Connect this component to Redux with the <code>connect</code> method from the <code>ReactRedux</code> global object, and call it immediately on the <code>Presentational</code> component. Assign the result to a new <code>const</code> called <code>ConnectedComponent</code> that represents the connected component. That's it, now you're connected to Redux! Try changing either of <code>connect</code>'s arguments to <code>null</code> and observe the test results."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"const addMessage = (message) => {",
|
||||
" return {",
|
||||
" type: 'ADD',",
|
||||
" message: message",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const mapStateToProps = (state) => {",
|
||||
" return {",
|
||||
" messages: state",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const mapDispatchToProps = (dispatch) => {",
|
||||
" return {",
|
||||
" submitNewMessage: (message) => {",
|
||||
" dispatch(addMessage(message));",
|
||||
" }",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"class Presentational extends React.Component {",
|
||||
" constructor(props) {",
|
||||
" super(props);",
|
||||
" }",
|
||||
" render() {",
|
||||
" return <h3>This is a Presentational Component</h3>",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const connect = ReactRedux.connect;",
|
||||
"// change code below this line",
|
||||
""
|
||||
],
|
||||
"tail": [
|
||||
"",
|
||||
"const store = Redux.createStore(",
|
||||
" (state = '__INITIAL__STATE__', action) => state",
|
||||
");",
|
||||
"class AppWrapper extends React.Component {",
|
||||
" render() {",
|
||||
" return (",
|
||||
" <ReactRedux.Provider store = {store}>",
|
||||
" <ConnectedComponent/>",
|
||||
" </ReactRedux.Provider>",
|
||||
" );",
|
||||
" }",
|
||||
"};",
|
||||
"ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should render.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'The <code>Presentational</code> component should render.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should receive a prop <code>messages</code> via <code>connect</code>.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return props.messages === '__INITIAL__STATE__'; })(), 'The <code>Presentational</code> component should receive a prop <code>messages</code> via <code>connect</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should receive a prop <code>submitNewMessage</code> via <code>connect</code>.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return typeof props.submitNewMessage === 'function'; })(), 'The <code>Presentational</code> component should receive a prop <code>submitNewMessage</code> via <code>connect</code>.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"const addMessage = (message) => {\n return {\n type: 'ADD',\n message: message\n }\n};\n\nconst mapStateToProps = (state) => {\n return {\n messages: state\n }\n};\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: (message) => {\n dispatch(addMessage(message));\n }\n }\n};\n\nclass Presentational extends React.Component {\n constructor(props) {\n super(props);\n }\n render() {\n return <h3>This is a Presentational Component</h3>\n }\n};\n\nconst connect = ReactRedux.connect;\n// change code below this line\n\nconst ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational); \n"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036148",
|
||||
"title": "Connect Redux to the Messages App",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"Now that you understand how to use <code>connect</code> to connect React to Redux, you can apply what you've learned to your React component that handles messages.",
|
||||
"In the last lesson, the component you connected to Redux was named <code>Presentational</code>, and this wasn't arbitrary. This term <i>generally</i> refers to React components that are not directly connected to Redux. They are simply responsible for the presentation of UI and do this as a function of the props they receive. By contrast, container components are connected to Redux. These are typically responsible for dispatching actions to the store and often pass store state to child components as props.",
|
||||
"<hr>",
|
||||
"The code editor has all the code you've written in this section so far. The only change is that the React component is renamed to <code>Presentational</code>. Create a new component held in a constant called <code>Container</code> that uses <code>connect</code> to connect the <code>Presentational</code> component to Redux. Then, in the <code>AppWrapper</code>, render the React Redux <code>Provider</code> component. Pass <code>Provider</code> the Redux <code>store</code> as a prop and render <code>Container</code> as a child. Once everything is setup, you will see the messages app rendered to the page again."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// Redux:",
|
||||
"const ADD = 'ADD';",
|
||||
"",
|
||||
"const addMessage = (message) => {",
|
||||
" return {",
|
||||
" type: ADD,",
|
||||
" message: message",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const messageReducer = (state = [], action) => {",
|
||||
" switch (action.type) {",
|
||||
" case ADD:",
|
||||
" return [",
|
||||
" ...state,",
|
||||
" action.message",
|
||||
" ];",
|
||||
" default:",
|
||||
" return state;",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const store = Redux.createStore(messageReducer);",
|
||||
"",
|
||||
"// React:",
|
||||
"class Presentational extends React.Component {",
|
||||
" constructor(props) {",
|
||||
" super(props);",
|
||||
" this.state = {",
|
||||
" input: '',",
|
||||
" messages: []",
|
||||
" }",
|
||||
" this.handleChange = this.handleChange.bind(this);",
|
||||
" this.submitMessage = this.submitMessage.bind(this);",
|
||||
" }",
|
||||
" handleChange(event) {",
|
||||
" this.setState({",
|
||||
" input: event.target.value",
|
||||
" });",
|
||||
" }",
|
||||
" submitMessage() {",
|
||||
" const currentMessage = this.state.input;",
|
||||
" this.setState({",
|
||||
" input: '',",
|
||||
" messages: this.state.messages.concat(currentMessage)",
|
||||
" });",
|
||||
" }",
|
||||
" render() {",
|
||||
" return (",
|
||||
" <div>",
|
||||
" <h2>Type in a new Message:</h2>",
|
||||
" <input",
|
||||
" value={this.state.input}",
|
||||
" onChange={this.handleChange}/><br/>",
|
||||
" <button onClick={this.submitMessage}>Submit</button>",
|
||||
" <ul>",
|
||||
" {this.state.messages.map( (message, idx) => {",
|
||||
" return (",
|
||||
" <li key={idx}>{message}</li>",
|
||||
" )",
|
||||
" })",
|
||||
" }",
|
||||
" </ul>",
|
||||
" </div>",
|
||||
" );",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"// React-Redux:",
|
||||
"const mapStateToProps = (state) => {",
|
||||
" return { messages: state }",
|
||||
"};",
|
||||
"",
|
||||
"const mapDispatchToProps = (dispatch) => {",
|
||||
" return {",
|
||||
" submitNewMessage: (newMessage) => {",
|
||||
" dispatch(addMessage(newMessage))",
|
||||
" }",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const Provider = ReactRedux.Provider;",
|
||||
"const connect = ReactRedux.connect;",
|
||||
"",
|
||||
"// define the Container component here:",
|
||||
"",
|
||||
"",
|
||||
"class AppWrapper extends React.Component {",
|
||||
" constructor(props) {",
|
||||
" super(props);",
|
||||
" }",
|
||||
" render() {",
|
||||
" // complete the return statement:",
|
||||
" return (null);",
|
||||
" }",
|
||||
"};"
|
||||
],
|
||||
"tail": "ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>AppWrapper</code> should render to the page.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'The <code>AppWrapper</code> should render to the page.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })(), 'The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should receive <code>messages</code> from the Redux store as a prop.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })(), 'The <code>Presentational</code> component should receive <code>messages</code> from the Redux store as a prop.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should receive the <code>submitMessage</code> action creator as a prop.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })(), 'The <code>Presentational</code> component should receive the <code>submitMessage</code> action creator as a prop.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"// Redux:\nconst ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message: message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);\n\n// React:\nclass Presentational extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: '',\n messages: []\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n const currentMessage = this.state.input;\n this.setState({\n input: '',\n messages: this.state.messages.concat(currentMessage)\n });\n }\n render() {\n return (\n <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.state.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};\n\n// React-Redux:\nconst mapStateToProps = (state) => {\n return { messages: state }\n};\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: (newMessage) => {\n dispatch(addMessage(newMessage))\n }\n }\n};\n\nconst Provider = ReactRedux.Provider;\nconst connect = ReactRedux.connect;\n\n// define the Container component here:\nconst Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);\n\nclass AppWrapper extends React.Component {\n constructor(props) {\n super(props);\n }\n render() {\n // complete the return statement:\n return (\n <Provider store={store}>\n <Container/>\n </Provider>\n );\n }\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d4036149",
|
||||
"title": "Extract Local State into Redux",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"You're almost done! Recall that you wrote all the Redux code so that Redux could control the state management of your React messages app. Now that Redux is connected, you need to extract the state management out of the <code>Presentational</code> component and into Redux. Currently, you have Redux connected, but you are handling the state locally within the <code>Presentational</code> component.",
|
||||
"<hr>",
|
||||
"In the <code>Presentational</code> component, first, remove the <code>messages</code> property in the local <code>state</code>. These messages will be managed by Redux. Next, modify the <code>submitMessage()</code> method so that it dispatches <code>submitNewMessage()</code> from <code>this.props</code>, and pass in the current message input from local <code>state</code> as an argument. Because you removed <code>messages</code> from local state, remove the <code>messages</code> property from the call to <code>this.setState()</code> here as well. Finally, modify the <code>render()</code> method so that it maps over the messages received from <code>props</code> rather than <code>state</code>.",
|
||||
"Once these changes are made, the app will continue to function the same, except Redux manages the state. This example also illustrates how a component may have local <code>state</code>: your component still tracks user input locally in its own <code>state</code>. You can see how Redux provides a useful state management framework on top of React. You achieved the same result using only React's local state at first, and this is usually possible with simple apps. However, as your apps become larger and more complex, so does your state management, and this is the problem Redux solves."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// Redux:",
|
||||
"const ADD = 'ADD';",
|
||||
"",
|
||||
"const addMessage = (message) => {",
|
||||
" return {",
|
||||
" type: ADD,",
|
||||
" message: message",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const messageReducer = (state = [], action) => {",
|
||||
" switch (action.type) {",
|
||||
" case ADD:",
|
||||
" return [",
|
||||
" ...state,",
|
||||
" action.message",
|
||||
" ];",
|
||||
" default:",
|
||||
" return state;",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const store = Redux.createStore(messageReducer);",
|
||||
"",
|
||||
"// React:",
|
||||
"const Provider = ReactRedux.Provider;",
|
||||
"const connect = ReactRedux.connect;",
|
||||
"",
|
||||
"// Change code below this line",
|
||||
"class Presentational extends React.Component {",
|
||||
" constructor(props) {",
|
||||
" super(props);",
|
||||
" this.state = {",
|
||||
" input: '',",
|
||||
" messages: []",
|
||||
" }",
|
||||
" this.handleChange = this.handleChange.bind(this);",
|
||||
" this.submitMessage = this.submitMessage.bind(this);",
|
||||
" }",
|
||||
" handleChange(event) {",
|
||||
" this.setState({",
|
||||
" input: event.target.value",
|
||||
" });",
|
||||
" }",
|
||||
" submitMessage() {",
|
||||
" this.setState({",
|
||||
" input: '',",
|
||||
" messages: this.state.messages.concat(this.state.input)",
|
||||
" });",
|
||||
" }",
|
||||
" render() {",
|
||||
" return (",
|
||||
" <div>",
|
||||
" <h2>Type in a new Message:</h2>",
|
||||
" <input",
|
||||
" value={this.state.input}",
|
||||
" onChange={this.handleChange}/><br/>",
|
||||
" <button onClick={this.submitMessage}>Submit</button>",
|
||||
" <ul>",
|
||||
" {this.state.messages.map( (message, idx) => {",
|
||||
" return (",
|
||||
" <li key={idx}>{message}</li>",
|
||||
" )",
|
||||
" })",
|
||||
" }",
|
||||
" </ul>",
|
||||
" </div>",
|
||||
" );",
|
||||
" }",
|
||||
"};",
|
||||
"// Change code above this line",
|
||||
"",
|
||||
"const mapStateToProps = (state) => {",
|
||||
" return {messages: state}",
|
||||
"};",
|
||||
"",
|
||||
"const mapDispatchToProps = (dispatch) => {",
|
||||
" return {",
|
||||
" submitNewMessage: (message) => {",
|
||||
" dispatch(addMessage(message))",
|
||||
" }",
|
||||
" }",
|
||||
"};",
|
||||
"",
|
||||
"const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);",
|
||||
"",
|
||||
"class AppWrapper extends React.Component {",
|
||||
" render() {",
|
||||
" return (",
|
||||
" <Provider store={store}>",
|
||||
" <Container/>",
|
||||
" </Provider>",
|
||||
" );",
|
||||
" }",
|
||||
"};"
|
||||
],
|
||||
"tail": "ReactDOM.render(<AppWrapper />, document.getElementById('root'))"
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The <code>AppWrapper</code> should render to the page.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'The <code>AppWrapper</code> should render to the page.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })(), 'The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })(), 'The <code>Presentational</code> component should render an <code>h2</code>, <code>input</code>, <code>button</code>, and <code>ul</code> elements.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should receive <code>messages</code> from the Redux store as a prop.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })(), 'The <code>Presentational</code> component should receive <code>messages</code> from the Redux store as a prop.');"
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should receive the <code>submitMessage</code> action creator as a prop.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })(), 'The <code>Presentational</code> component should receive the <code>submitMessage</code> action creator as a prop.');"
|
||||
},
|
||||
{
|
||||
"text": "The state of the <code>Presentational</code> component should contain one property, <code>input</code>, which is initialized to an empty string.",
|
||||
"testString": "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalState = mockedComponent.find('Presentational').instance().state; return typeof PresentationalState.input === 'string' && Object.keys(PresentationalState).length === 1; })(), 'The state of the <code>Presentational</code> component should contain one property, <code>input</code>, which is initialized to an empty string.');"
|
||||
},
|
||||
{
|
||||
"text": "Typing in the <code>input</code> element should update the state of the <code>Presentational</code> component.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const testValue = '__MOCK__INPUT__'; const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); let initialInput = mockedComponent.find('Presentational').find('input'); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); const updatedInput = updated.find('Presentational').find('input'); assert(initialInput.props().value === '' && updatedInput.props().value === '__MOCK__INPUT__', 'Typing in the <code>input</code> element should update the state of the <code>Presentational</code> component.'); }; "
|
||||
},
|
||||
{
|
||||
"text": "Dispatching the <code>submitMessage</code> on the <code>Presentational</code> component should update Redux store and clear the input in local state.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find('Presentational').props(); const testValue = '__TEST__EVENT__INPUT__'; const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const clickButton = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterChange = await changed(); const afterChangeInput = afterChange.find('input').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find('Presentational').props(); assert(beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find('input').props().value === '', 'Dispatching the <code>submitMessage</code> on the <code>Presentational</code> component should update Redux store and clear the input in local state.'); }; "
|
||||
},
|
||||
{
|
||||
"text": "The <code>Presentational</code> component should render the <code>messages</code> from the Redux store.",
|
||||
"testString": "async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find('Presentational').props(); const testValue = '__TEST__EVENT__INPUT__'; const causeChange = (c, v) => c.find('input').simulate('change', { target: { value: v }}); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const clickButton = () => { mockedComponent.find('button').simulate('click'); return waitForIt(() => mockedComponent )}; const afterChange = await changed(); const afterChangeInput = afterChange.find('input').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find('Presentational').props(); assert(beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find('input').props().value === '' && afterClick.find('ul').childAt(0).text() === testValue, 'The <code>Presentational</code> component should render the <code>messages</code> from the Redux store.'); }; "
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"// Redux:\nconst ADD = 'ADD';\n\nconst addMessage = (message) => {\n return {\n type: ADD,\n message: message\n }\n};\n\nconst messageReducer = (state = [], action) => {\n switch (action.type) {\n case ADD:\n return [\n ...state,\n action.message\n ];\n default:\n return state;\n }\n};\n\nconst store = Redux.createStore(messageReducer);\n\n// React:\nconst Provider = ReactRedux.Provider;\nconst connect = ReactRedux.connect;\n\n// Change code below this line\nclass Presentational extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n input: ''\n }\n this.handleChange = this.handleChange.bind(this); \n this.submitMessage = this.submitMessage.bind(this); \n }\n handleChange(event) {\n this.setState({\n input: event.target.value\n });\n }\n submitMessage() {\n this.props.submitNewMessage(this.state.input);\n this.setState({\n input: ''\n });\n }\n render() {\n return (\n <div>\n <h2>Type in a new Message:</h2>\n <input\n value={this.state.input}\n onChange={this.handleChange}/><br/>\n <button onClick={this.submitMessage}>Submit</button>\n <ul>\n {this.props.messages.map( (message, idx) => {\n return (\n <li key={idx}>{message}</li>\n )\n })\n }\n </ul>\n </div>\n );\n }\n};\n// Change code above this line\n\nconst mapStateToProps = (state) => {\n return {messages: state}\n};\n\nconst mapDispatchToProps = (dispatch) => {\n return {\n submitNewMessage: (message) => {\n dispatch(addMessage(message))\n }\n }\n};\n\nconst Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);\n\nclass AppWrapper extends React.Component {\n render() {\n return (\n <Provider store={store}>\n <Container/>\n </Provider>\n );\n }\n};"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
},
|
||||
{
|
||||
"id": "5a24c314108439a4d403614a",
|
||||
"title": "Moving Forward From Here",
|
||||
"releasedOn": "December 25, 2017",
|
||||
"description": [
|
||||
"Congratulations! You finished the lessons on React and Redux. There's one last item worth pointing out before you move on. Typically, you won't write React apps in a code editor like this. This challenge gives you a glimpse of what the syntax looks like if you're working with npm and a file system on your own machine. The code should look similar, except for the use of <code>import</code> statements (these pull in all of the dependencies that have been provided for you in the challenges). The \"Managing Packages with npm\" section covers npm in more detail.",
|
||||
"Finally, writing React and Redux code generally requires some configuration. This can get complicated quickly. If you are interested in experimenting on your own machine, the",
|
||||
"<a id='CRA' target ='_blank' href='https://github.com/facebookincubator/create-react-app'>Create React App</a> comes configured and ready to go.",
|
||||
"Alternatively, you can enable Babel as a JavaScript Preprocessor in CodePen, add React and ReactDOM as external JavaScript resources, and work there as well.",
|
||||
"<hr>",
|
||||
"Log the message <code>'Now I know React and Redux!'</code> to the console."
|
||||
],
|
||||
"files": {
|
||||
"indexjsx": {
|
||||
"key": "indexjsx",
|
||||
"ext": "jsx",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// import React from 'react'",
|
||||
"// import ReactDOM from 'react-dom'",
|
||||
"// import { Provider, connect } from 'react-redux'",
|
||||
"// import { createStore, combineReducers, applyMiddleware } from 'redux'",
|
||||
"// import thunk from 'redux-thunk'",
|
||||
"",
|
||||
"// import rootReducer from './redux/reducers'",
|
||||
"// import App from './components/App'",
|
||||
"",
|
||||
"// const store = createStore(",
|
||||
"// rootReducer,",
|
||||
"// applyMiddleware(thunk)",
|
||||
"// );",
|
||||
"",
|
||||
"// ReactDOM.render(",
|
||||
"// <Provider store={store}>",
|
||||
"// <App/>",
|
||||
"// </Provider>,",
|
||||
"// document.getElementById('root')",
|
||||
"// );",
|
||||
"",
|
||||
"// change code below this line",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"text": "The message <code>Now I know React and Redux!</code> should be logged to the console.",
|
||||
"testString": "assert(editor.getValue().includes('console.log(\"Now I know React and Redux!\")') || editor.getValue().includes('console.log(\\'Now I know React and Redux!\\')'), 'The message <code>Now I know React and Redux!</code> should be logged to the console.');"
|
||||
}
|
||||
],
|
||||
"solutions": [
|
||||
"console.log('Now I know React and Redux!');"
|
||||
],
|
||||
"challengeType": 6,
|
||||
"isRequired": false,
|
||||
"translations": {},
|
||||
"reactRedux": true
|
||||
}
|
||||
],
|
||||
"fileName": "03-front-end-libraries/react-and-redux.json",
|
||||
"superBlock": "front-end-libraries",
|
||||
"superOrder": 3
|
||||
}
|
3544
packages/learn/seed/challenges/03-front-end-libraries/react.json
Normal file
3544
packages/learn/seed/challenges/03-front-end-libraries/react.json
Normal file
File diff suppressed because it is too large
Load Diff
1125
packages/learn/seed/challenges/03-front-end-libraries/redux.json
Normal file
1125
packages/learn/seed/challenges/03-front-end-libraries/redux.json
Normal file
File diff suppressed because it is too large
Load Diff
675
packages/learn/seed/challenges/03-front-end-libraries/sass.json
Normal file
675
packages/learn/seed/challenges/03-front-end-libraries/sass.json
Normal file
@ -0,0 +1,675 @@
|
||||
{
|
||||
"name": "Sass",
|
||||
"order": 4,
|
||||
"time": "5 hours",
|
||||
"helpRoom": "Help",
|
||||
"challenges": [
|
||||
{
|
||||
"id": "587d7dbd367417b2b2512bb4",
|
||||
"title": "Store Data with Sass Variables",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"One feature of Sass that's different than CSS is it uses variables. They are declared and set to store data, similar to JavaScript.",
|
||||
"In JavaScript, variables are defined using the <code>let</code> and <code>const</code> keywords. In Sass, variables start with a <code>$</code> followed by the variable name.",
|
||||
"Here are a couple examples:",
|
||||
"<blockquote>$main-fonts: Arial, sans-serif;<br>$headings-color: green;<br><br>//To use variables:<br>h1 {<br> font-family: $main-fonts;<br> color: $headings-color;<br>}</blockquote>",
|
||||
"One example where variables are useful is when a number of elements need to be the same color. If that color is changed, the only place to edit the code is the variable value.",
|
||||
"<hr>",
|
||||
"Create a variable <code>$text-color</code> and set it to red. Then change the value of the <code>color</code> property for the <code>.blog-post</code> and <code>h2</code> to the <code>$text-color</code> variable."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should have a Sass variable declared for <code>$text-color</code> with a value of red.",
|
||||
"testString": "assert(code.match(/\\$text-color:\\s*?red;/g), 'Your code should have a Sass variable declared for <code>$text-color</code> with a value of red.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should use the <code>$text-color</code> variable to change the <code>color</code> for the <code>.blog-post</code> and <code>h2</code> items.",
|
||||
"testString": "assert(code.match(/color:\\s*?\\$text-color;/g), 'Your code should use the <code>$text-color</code> variable to change the <code>color</code> for the <code>.blog-post</code> and <code>h2</code> items.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.blog-post</code> element should have a </code>color</code> of red.",
|
||||
"testString": "assert($('.blog-post').css('color') == 'rgb(255, 0, 0)', 'Your <code>.blog-post</code> element should have a </code>color</code> of red.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>h2</code> elements should have a </code>color</code> of red.",
|
||||
"testString": "assert($('h2').css('color') == 'rgb(255, 0, 0)', 'Your <code>h2</code> elements should have a </code>color</code> of red.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" ",
|
||||
" ",
|
||||
" .header{",
|
||||
" text-align: center;",
|
||||
" }",
|
||||
" .blog-post, h2 {",
|
||||
" color: red;",
|
||||
" }",
|
||||
"</style>",
|
||||
"",
|
||||
"<h1 class=\"header\">Learn Sass</h1>",
|
||||
"<div class=\"blog-post\">",
|
||||
" <h2>Some random title</h2>",
|
||||
" <p>This is a paragraph with some random text in it</p>",
|
||||
"</div>",
|
||||
"<div class=\"blog-post\">",
|
||||
" <h2>Header #2</h2>",
|
||||
" <p>Here is some more random text.</p>",
|
||||
"</div>",
|
||||
"<div class=\"blog-post\">",
|
||||
" <h2>Here is another header</h2>",
|
||||
" <p>Even more random text within a paragraph</p>",
|
||||
"</div>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbd367417b2b2512bb5",
|
||||
"title": "Nest CSS with Sass",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"Sass allows <code>nesting</code> of CSS rules, which is a useful way of organizing a style sheet.",
|
||||
"Normally, each element is targeted on a different line to style it, like so:",
|
||||
"<blockquote>nav {<br> background-color: red;<br>}<br><br>nav ul {<br> list-style: none;<br>}<br><br>nav ul li {<br> display: inline-block;<br>}</blockquote>",
|
||||
"For a large project, the CSS file will have many lines and rules. This is where <code>nesting</code> can help organize your code by placing child style rules within the respective parent elements:",
|
||||
"<blockquote>nav {<br> background-color: red;<br><br> ul {<br> list-style: none;<br><br> li {<br> display: inline-block;<br> }<br> }<br>}<br></blockquote>",
|
||||
"<hr>",
|
||||
"Use the <code>nesting</code> technique shown above to re-organize the CSS rules for both children of <code>.blog-post</code> element. For testing purposes, the <code>h1</code> should come before the <code>p</code> element."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should re-organize the CSS rules so the <code>h1</code> and <code>p</code> are nested in the <code>.blog-post</code> parent element.",
|
||||
"testString": "assert(code.match(/\\.blog-post\\s*?{\\s*?h1\\s*?{\\s*?text-align:\\s*?center;\\s*?color:\\s*?blue;\\s*?}\\s*?p\\s*?{\\s*?font-size:\\s*?20px;\\s*?}\\s*?}/gi), 'Your code should re-organize the CSS rules so the <code>h1</code> and <code>p</code> are nested in the <code>.blog-post</code> parent element.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" .blog-post {",
|
||||
" ",
|
||||
" }",
|
||||
" h1 {",
|
||||
" text-align: center;",
|
||||
" color: blue;",
|
||||
" }",
|
||||
" p {",
|
||||
" font-size: 20px;",
|
||||
" }",
|
||||
"</style>",
|
||||
"",
|
||||
"<div class=\"blog-post\">",
|
||||
" <h1>Blog Title</h1>",
|
||||
" <p>This is a paragraph</p>",
|
||||
"</div>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbd367417b2b2512bb6",
|
||||
"title": "Create Reusable CSS with Mixins",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"In Sass, a <code>mixin</code> is a group of CSS declarations that can be reused throughout the style sheet.",
|
||||
"Newer CSS features take time before they are fully adopted and ready to use in all browsers. As features are added to browsers, CSS rules using them may need vendor prefixes. Consider \"box-shadow\":",
|
||||
"<blockquote>div {<br> -webkit-box-shadow: 0px 0px 4px #fff;<br> -moz-box-shadow: 0px 0px 4px #fff;<br> -ms-box-shadow: 0px 0px 4px #fff;<br> box-shadow: 0px 0px 4px #fff;<br>}</blockquote>",
|
||||
"It's a lot of typing to re-write this rule for all the elements that have a <code>box-shadow</code>, or to change each value to test different effects.",
|
||||
"<code>Mixins</code> are like functions for CSS. Here is how to write one:",
|
||||
"<blockquote>@mixin box-shadow($x, $y, $blur, $c){ <br> -webkit-box-shadow: $x, $y, $blur, $c;<br> -moz-box-shadow: $x, $y, $blur, $c;<br> -ms-box-shadow: $x, $y, $blur, $c;<br> box-shadow: $x, $y, $blur, $c;<br>}</blockquote>",
|
||||
"The definition starts with <code>@mixin</code> followed by a custom name. The parameters (the <code>$x</code>, <code>$y</code>, <code>$blur</code>, and <code>$c</code> in the example above) are optional.",
|
||||
"Now any time a <code>box-shadow</code> rule is needed, only a single line calling the <code>mixin</code> replaces having to type all the vendor prefixes. A <code>mixin</code> is called with the <code>@include</code> directive:",
|
||||
"<blockquote>div {<br> @include box-shadow(0px, 0px, 4px, #fff);<br>}</blockquote>",
|
||||
"<hr>",
|
||||
"Write a <code>mixin</code> for <code>border-radius</code> and give it a <code>$radius</code> parameter. It should use all the vendor prefixes from the example. Then use the <code>border-radius</code> <code>mixin</code> to give the <code>#awesome</code> element a border radius of 15px."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should declare a <code>mixin</code> named <code>border-radius</code> which has a parameter named <code>$radius</code>.",
|
||||
"testString": "assert(code.match(/@mixin\\s+?border-radius\\(\\s*?\\$radius\\s*?\\)\\s*?{/gi), 'Your code should declare a <code>mixin</code> named <code>border-radius</code> which has a parameter named <code>$radius</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should include the <code>-webkit-border-radius</code> vender prefix that uses the <code>$radius</code> parameter.",
|
||||
"testString": "assert(code.match(/-webkit-border-radius:\\s*?\\$radius;/gi), 'Your code should include the <code>-webkit-border-radius</code> vender prefix that uses the <code>$radius</code> parameter.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should include the <code>-moz-border-radius</code> vender prefix that uses the <code>$radius</code> parameter.",
|
||||
"testString": "assert(code.match(/-moz-border-radius:\\s*?\\$radius;/gi), 'Your code should include the <code>-moz-border-radius</code> vender prefix that uses the <code>$radius</code> parameter.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should include the <code>-ms-border-radius</code> vender prefix that uses the <code>$radius</code> parameter.",
|
||||
"testString": "assert(code.match(/-ms-border-radius:\\s*?\\$radius;/gi), 'Your code should include the <code>-ms-border-radius</code> vender prefix that uses the <code>$radius</code> parameter.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should include the general <code>border-radius</code> rule that uses the <code>$radius</code> parameter.",
|
||||
"testString": "assert(code.match(/border-radius:\\s*?\\$radius;/gi).length == 4, 'Your code should include the general <code>border-radius</code> rule that uses the <code>$radius</code> parameter.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should call the <code>border-radius mixin</code> using the <code>@include</code> keyword, setting it to 15px.",
|
||||
"testString": "assert(code.match(/@include\\s+?border-radius\\(\\s*?15px\\s*?\\);/gi), 'Your code should call the <code>border-radius mixin</code> using the <code>@include</code> keyword, setting it to 15px.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" #awesome {",
|
||||
" width: 150px;",
|
||||
" height: 150px;",
|
||||
" background-color: green;",
|
||||
" ",
|
||||
" }",
|
||||
"</style>",
|
||||
"",
|
||||
"<div id=\"awesome\"></div>",
|
||||
" "
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbe367417b2b2512bb8",
|
||||
"title": "Use @if and @else to Add Logic To Your Styles",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"The <code>@if</code> directive in Sass is useful to test for a specific case - it works just like the <code>if</code> statement in JavaScript</code>.",
|
||||
"<blockquote>@mixin make-bold($bool) {<br> @if $bool == bold { font-weight: bold; }<br>}</blockquote>",
|
||||
"And just like in JavaScript, <code>@else if</code> and <code>@else</code> test for more conditions:",
|
||||
"<blockquote>@mixin text-effect($val) {<br> @if $val == danger {color: red;}<br> @else if $val == alert {color: yellow;}<br> @else if $val == success {color: green;}<br> @else {color: black;}<br>}</blockquote>",
|
||||
"<hr>",
|
||||
"Create a <code>mixin</code> called <code>border-stroke</code> that takes a parameter <code>$val</code>. The <code>mixin</code> should check for the following conditions using <code>@if</code>, <code>@else if</code>, and <code>@else</code>:",
|
||||
"<blockquote>light - 1px solid black<br>medium - 3px solid black<br>heavy - 6px solid black<br>none - no border</blockquote>"
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should declare a <code>mixin</code> named <code>border-stroke</code> which has a parameter named <code>$val</code>.",
|
||||
"testString": "assert(code.match(/@mixin\\s+?border-stroke\\s*?\\(\\s*?\\$val\\s*?\\)\\s*?{/gi), 'Your code should declare a <code>mixin</code> named <code>border-stroke</code> which has a parameter named <code>$val</code>.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>mixin</code> should have an <code>@if</code> statement to check if <code>$val</code> is light, and to set the <code>border</code> to 1px solid black.",
|
||||
"testString": "assert(code.match(/@if\\s+?\\$val\\s*?===?\\s*?light\\s*?{\\s*?border\\s*?:\\s*?1px\\s+?solid\\s+?black\\s*?;\\s*?}/gi), 'Your <code>mixin</code> should have an <code>@if</code> statement to check if <code>$val</code> is light, and to set the <code>border</code> to 1px solid black.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>mixin</code> should have an <code>@else if</code> statement to check if <code>$val</code> is medium, and to set the <code>border</code> to 3px solid black.",
|
||||
"testString": "assert(code.match(/@else\\s+?if\\s+?\\$val\\s*?===?\\s*?medium\\s*?{\\s*?border\\s*?:\\s*?3px\\s+?solid\\s+?black\\s*?;\\s*?}/gi), 'Your <code>mixin</code> should have an <code>@else if</code> statement to check if <code>$val</code> is medium, and to set the <code>border</code> to 3px solid black.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>mixin</code> should have an <code>@else if</code> statement to check if <code>$val</code> is heavy, and to set the <code>border</code> to 6px solid black.",
|
||||
"testString": "assert(code.match(/@else\\s+?if\\s+?\\$val\\s*?===?\\s*?heavy\\s*?{\\s*?border\\s*?:\\s*?6px\\s+?solid\\s+?black\\s*?;\\s*?}/gi), 'Your <code>mixin</code> should have an <code>@else if</code> statement to check if <code>$val</code> is heavy, and to set the <code>border</code> to 6px solid black.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>mixin</code> should have an <code>@else</code> statement to set the <code>border</code> to none.",
|
||||
"testString": "assert(code.match(/@else\\s*?{\\s*?border\\s*?:\\s*?none\\s*?;\\s*?}/gi), 'Your <code>mixin</code> should have an <code>@else</code> statement to set the <code>border</code> to none.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" #box {",
|
||||
" width: 150px;",
|
||||
" height: 150px;",
|
||||
" background-color: red;",
|
||||
" @include border-stroke(medium);",
|
||||
" } ",
|
||||
"</style>",
|
||||
"",
|
||||
"<div id=\"box\"></div>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbe367417b2b2512bb9",
|
||||
"title": "Use @for to Create a Sass Loop",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"The <code>@for</code> directive adds styles in a loop, very similar to a <code>for</code> loop in JavaScript.",
|
||||
"<code>@for</code> is used in two ways: \"start through end\" or \"start to end\". The main difference is that \"start to end\" <em>excludes</em> the end number, and \"start through end\" <em>includes</em> the end number.",
|
||||
"Here's a start <b>through</b> end example:",
|
||||
"<blockquote>@for $i from 1 through 12 {<br> .col-#{$i} { width: 100%/12 * $i; }<br>}</blockquote>",
|
||||
"The <code>#{$i}</code> part is the syntax to combine a variable (<code>i</code>) with text to make a string. When the Sass file is converted to CSS, it looks like this:",
|
||||
"<blockquote>.col-1 {<br> width: 8.33333%;<br>}<br><br>.col-2 {<br> width: 16.66667%;<br>}<br><br>...<br><br>.col-12 {<br> width: 100%;<br>}</blockquote>",
|
||||
"This is a powerful way to create a grid layout. Now you have twelve options for column widths available as CSS classes.",
|
||||
"<hr>",
|
||||
"Write a <code>@for</code> directive that takes a variable <code>$j</code> that goes from 1 <b>to</b> 6.",
|
||||
"It should create 5 classes called <code>.text-1</code> to <code>.text-5</code> where each has a <code>font-size</code> set to 10px multiplied by the index."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use the <code>@for</code> directive.",
|
||||
"testString": "assert(code.match(/@for /g), 'Your code should use the <code>@for</code> directive.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-1</code> class should have a <code>font-size</code> of 10px.",
|
||||
"testString": "assert($('.text-1').css('font-size') == '10px', 'Your <code>.text-1</code> class should have a <code>font-size</code> of 10px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-2</code> class should have a <code>font-size</code> of 20px.",
|
||||
"testString": "assert($('.text-2').css('font-size') == '20px', 'Your <code>.text-2</code> class should have a <code>font-size</code> of 20px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-3</code> class should have a <code>font-size</code> of 30px.",
|
||||
"testString": "assert($('.text-3').css('font-size') == '30px', 'Your <code>.text-3</code> class should have a <code>font-size</code> of 30px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-4</code> class should have a <code>font-size</code> of 40px.",
|
||||
"testString": "assert($('.text-4').css('font-size') == '40px', 'Your <code>.text-4</code> class should have a <code>font-size</code> of 40px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-5</code> class should have a <code>font-size</code> of 50px.",
|
||||
"testString": "assert($('.text-5').css('font-size') == '50px', 'Your <code>.text-5</code> class should have a <code>font-size</code> of 50px.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<p class=\"text-1\">Hello</p>",
|
||||
"<p class=\"text-2\">Hello</p>",
|
||||
"<p class=\"text-3\">Hello</p>",
|
||||
"<p class=\"text-4\">Hello</p>",
|
||||
"<p class=\"text-5\">Hello</p>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbf367417b2b2512bba",
|
||||
"title": "Use @each to Map Over Items in a List",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"The last challenge showed how the <code>@for</code> directive uses a starting and ending value to loop a certain number of times. Sass also offers the <code>@each</code> directive which loops over each item in a list or map.",
|
||||
"On each iteration, the variable gets assigned to the current value from the list or map.",
|
||||
"<blockquote>@each $color in blue, red, green {<br> .#{$color}-text {color: $color;}<br>}</blockquote>",
|
||||
"A map has slightly different syntax. Here's an example:",
|
||||
"<blockquote>$colors: (color1: blue, color2: red, color3: green);<br><br>@each $key, $color in $colors {<br> .#{$color}-text {color: $color;}<br>}</blockquote>",
|
||||
"Note that the <code>$key</code> variable is needed to reference the keys in the map. Otherwise, the compiled CSS would have <code>color1</code>, <code>color2</code>... in it.",
|
||||
"Both of the above code examples are converted into the following CSS:",
|
||||
"<blockquote>.blue-text {<br> color: blue;<br>}<br><br>.red-text {<br> color: red;<br>}<br><br>.green-text {<br> color: green;<br>}</blockquote>",
|
||||
"<hr>",
|
||||
"Write an <code>@each</code> directive that goes through a list: <code>blue, black, red</code> and assigns each variable to a <code>.color-bg</code> class, where the \"color\" part changes for each item.",
|
||||
"Each class should set the <code>background-color</code> the respective color."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use the <code>@each</code> directive.",
|
||||
"testString": "assert(code.match(/@each /g), 'Your code should use the <code>@each</code> directive.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.blue-bg</code> class should have a <code>background-color</code> of blue.",
|
||||
"testString": "assert($('blue-bg').css('background-color') == 'rgb(0, 0, 255)', 'Your <code>.blue-bg</code> class should have a <code>background-color</code> of blue.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.black-bg</code> class should have a <code>background-color</code> of black.",
|
||||
"testString": "assert($('black-bg').css('background-color') == 'rgb(0, 0, 0)', 'Your <code>.black-bg</code> class should have a <code>background-color</code> of black.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.red-bg</code> class should have a <code>background-color</code> of red.",
|
||||
"testString": "assert($('red-bg').css('background-color') == 'rgb(255, 0, 0)', 'Your <code>.red-bg</code> class should have a <code>background-color</code> of red.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" div {",
|
||||
" height: 200px;",
|
||||
" width: 200px;",
|
||||
" }",
|
||||
"</style>",
|
||||
"",
|
||||
"<div class=\"blue-bg\"></div>",
|
||||
"<div class=\"black-bg\"></div>",
|
||||
"<div class=\"red-bg\"></div>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbf367417b2b2512bbb",
|
||||
"title": "Apply a Style Until a Condition is Met with @while",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"The <code>@while</code> directive is an option with similar functionality to the JavaScript <code>while</code> loop. It creates CSS rules until a condition is met.",
|
||||
"The <code>@for</code> challenge gave an example to create a simple grid system. This can also work with <code>@while</code>.",
|
||||
"<blockquote>$x: 1;<br>@while $x < 13 {<br> .col-#{$x} { width: 100%/12 * $x;}<br> $x: $x + 1;<br>}</blockquote>",
|
||||
"First, define a variable <code>$x</code> and set it to 1. Next, use the <code>@while</code> directive to create the grid system <i>while</i> <code>$x</code> is less than 13.",
|
||||
"After setting the CSS rule for <code>width</code>, <code>$x</code> is incremented by 1 to avoid an infinite loop.",
|
||||
"<hr>",
|
||||
"Use <code>@while</code> to create a series of classes with different <code>font-sizes</code>.",
|
||||
"There should be 10 different classes from <code>text-1</code> to <code>text-10</code>. Then set <code>font-size</code> to 5px multiplied by the current index number. Make sure to avoid an infinite loop!"
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use the <code>@while</code> directive.",
|
||||
"testString": "assert(code.match(/@while /g), 'Your code should use the <code>@while</code> directive.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should set an index variable to 1 to start.",
|
||||
"testString": "assert(code.match(/\\$.*:\\s*?1;/gi), 'Your code should set an index variable to 1 to start.');"
|
||||
},
|
||||
{
|
||||
"text": "Your code should increment the counter variable.",
|
||||
"testString": "assert(code.match(/\\$(.*):\\s*?\\$\\1\\s*?\\+\\s*?1;/gi), 'Your code should increment the counter variable.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-1</code> class should have a <code>font-size</code> of 5px.",
|
||||
"testString": "assert($('.text-1').css('font-size') == '5px', 'Your <code>.text-1</code> class should have a <code>font-size</code> of 5px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-2</code> class should have a <code>font-size</code> of 10px.",
|
||||
"testString": "assert($('.text-2').css('font-size') == '10px', 'Your <code>.text-2</code> class should have a <code>font-size</code> of 10px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-3</code> class should have a <code>font-size</code> of 15px.",
|
||||
"testString": "assert($('.text-3').css('font-size') == '15px', 'Your <code>.text-3</code> class should have a <code>font-size</code> of 15px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-4</code> class should have a <code>font-size</code> of 20px.",
|
||||
"testString": "assert($('.text-4').css('font-size') == '20px', 'Your <code>.text-4</code> class should have a <code>font-size</code> of 20px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-5</code> class should have a <code>font-size</code> of 25px.",
|
||||
"testString": "assert($('.text-5').css('font-size') == '25px', 'Your <code>.text-5</code> class should have a <code>font-size</code> of 25px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-6</code> class should have a <code>font-size</code> of 30px.",
|
||||
"testString": "assert($('.text-6').css('font-size') == '30px', 'Your <code>.text-6</code> class should have a <code>font-size</code> of 30px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-7</code> class should have a <code>font-size</code> of 35px.",
|
||||
"testString": "assert($('.text-7').css('font-size') == '35px', 'Your <code>.text-7</code> class should have a <code>font-size</code> of 35px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-8</code> class should have a <code>font-size</code> of 40px.",
|
||||
"testString": "assert($('.text-8').css('font-size') == '40px', 'Your <code>.text-8</code> class should have a <code>font-size</code> of 40px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-9</code> class should have a <code>font-size</code> of 45px.",
|
||||
"testString": "assert($('.text-9').css('font-size') == '45px', 'Your <code>.text-9</code> class should have a <code>font-size</code> of 45px.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>.text-10</code> class should have a <code>font-size</code> of 50px.",
|
||||
"testString": "assert($('.text-10').css('font-size') == '50px', 'Your <code>.text-10</code> class should have a <code>font-size</code> of 50px.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"</style>",
|
||||
"",
|
||||
"<p class=\"text-1\">Hello</p>",
|
||||
"<p class=\"text-2\">Hello</p>",
|
||||
"<p class=\"text-3\">Hello</p>",
|
||||
"<p class=\"text-4\">Hello</p>",
|
||||
"<p class=\"text-5\">Hello</p>",
|
||||
"<p class=\"text-6\">Hello</p>",
|
||||
"<p class=\"text-7\">Hello</p>",
|
||||
"<p class=\"text-8\">Hello</p>",
|
||||
"<p class=\"text-9\">Hello</p>",
|
||||
"<p class=\"text-10\">Hello</p>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7dbf367417b2b2512bbc",
|
||||
"title": "Split Your Styles into Smaller Chunks with Partials",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"<code>Partials</code> in Sass are separate files that hold segments of CSS code. These are imported and used in other Sass files. This is a great way to group similar code into a module to keep it organized.",
|
||||
"Names for <code>partials</code> start with the underscore (<code>_</code>) character, which tells Sass it is a small segment of CSS and not to convert it into a CSS file. Also, Sass files end with the <code>.scss</code> file extension. To bring the code in the <code>partial</code> into another Sass file, use the <code>@import</code> directive.",
|
||||
"For example, if all your <code>mixins</code> are saved in a <code>partial</code> named \"_mixins.scss\", and they are needed in the \"main.scss\" file, this is how to use them in the main file:",
|
||||
"<blockquote>// In the main.scss file<br><br>@import 'mixins'</blockquote>",
|
||||
"Note that the underscore is not needed in the <code>import</code> statement - Sass understands it is a <code>partial</code>. Once a <code>partial</code> is imported into a file, all variables, <code>mixins</code>, and other code are available to use.",
|
||||
"<hr>",
|
||||
"Write an <code>@import</code> statement to import a <code>partial</code> named <code>_variables.scss</code> into the main.scss file."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use the <code>@import</code> directive, and should not include the underscore in the file name.",
|
||||
"testString": "assert(code.match(/@import\\s+?('|\")variables\\1/gi), 'Your code should use the <code>@import</code> directive, and should not include the underscore in the file name.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"// The main.scss file",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "587d7fa5367417b2b2512bbd",
|
||||
"title": "Extend One Set of CSS Styles to Another Element",
|
||||
"required": [
|
||||
{
|
||||
"src": "https://cdnjs.cloudflare.com/ajax/libs/sass.js/0.10.9/sass.sync.min.js",
|
||||
"raw": true
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
"Sass has a feature called <code>extend</code> that makes it easy to borrow the CSS rules from one element and build upon them in another.",
|
||||
"For example, the below block of CSS rules style a <code>.panel</code> class. It has a <code>background-color</code>, <code>height</code> and <code>border</code>.",
|
||||
"<blockquote>.panel{<br> background-color: red;<br> height: 70px;<br> border: 2px solid green;<br>}</blockquote>",
|
||||
"Now you want another panel called <code>.big-panel</code>. It has the same base properties as <code>.panel</code>, but also needs a <code>width</code> and <code>font-size</code>.",
|
||||
"It's possible to copy and paste the initial CSS rules from <code>.panel</code>, but the code becomes repetitive as you add more types of panels.",
|
||||
"The <code>extend</code> directive is a simple way to reuse the rules written for one element, then add more for another:",
|
||||
"<blockquote>.big-panel{<br> @extend .panel;<br> width: 150px;<br> font-size: 2em;<br>}</blockquote>",
|
||||
"The <code>.big-panel</code> will have the same properties as <code>.panel</code> in addition to the new styles.",
|
||||
"<hr>",
|
||||
"Make a class <code>.info-important</code> that extends <code>.info</code> and also has a <code>background-color</code> set to magenta."
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"text": "Your code should use the <code>@extend</code> directive to extend the <code>info</code> class.",
|
||||
"testString": "assert(code.match(/@extend\\s+?\\.info/g), 'Your code should use the <code>@extend</code> directive to extend the <code>info</code> class.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>info-important</code> class should have a <code>background-color</code> set to magenta.",
|
||||
"testString": "assert($('.info-important').css('background-color') == 'rgb(255, 0, 255)', 'Your <code>info-important</code> class should have a <code>background-color</code> set to magenta.');"
|
||||
},
|
||||
{
|
||||
"text": "Your <code>info-important</code> class should inherit the styling from the <code>info</code> class.",
|
||||
"testString": "assert($('.info-important').css('width') == '200px', 'Your <code>info-important</code> class should inherit the styling from the <code>info</code> class.');"
|
||||
}
|
||||
],
|
||||
"solutions": [],
|
||||
"hints": [],
|
||||
"type": "waypoint",
|
||||
"releasedOn": "Feb 17, 2017",
|
||||
"challengeType": 0,
|
||||
"translations": {},
|
||||
"files": {
|
||||
"indexjs": {
|
||||
"key": "indexhtml",
|
||||
"ext": "html",
|
||||
"name": "index",
|
||||
"contents": [
|
||||
"<style type='text/sass'>",
|
||||
" h3{",
|
||||
" text-align: center;",
|
||||
" }",
|
||||
" .info{",
|
||||
" width: 200px;",
|
||||
" border: 1px solid black;",
|
||||
" margin: 0 auto;",
|
||||
" }",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
"</style>",
|
||||
"<h3>Posts</h3>",
|
||||
"<div class=\"info-important\">",
|
||||
" <p>This is an important post. It should extend the class \".info\" and have its own CSS styles.</p>",
|
||||
"</div>",
|
||||
"",
|
||||
"<div class=\"info\">",
|
||||
" <p>This is a simple post. It has basic styling and can be extended for other uses.</p>",
|
||||
"</div>"
|
||||
],
|
||||
"head": "",
|
||||
"tail": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fileName": "03-front-end-libraries/sass.json",
|
||||
"superBlock": "front-end-libraries",
|
||||
"superOrder": 3
|
||||
}
|
@ -33,6 +33,13 @@ export const ChallengeNode = PropTypes.shape({
|
||||
isRequired: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
order: PropTypes.number,
|
||||
required: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
link: PropTypes.string,
|
||||
raw: PropTypes.string,
|
||||
src: PropTypes.string
|
||||
})
|
||||
),
|
||||
superOrder: PropTypes.number,
|
||||
superBlock: PropTypes.string,
|
||||
tail: PropTypes.arrayOf(PropTypes.string),
|
||||
|
@ -1,4 +1,4 @@
|
||||
export function createTypes(types, ns) {
|
||||
export function createTypes(types = [], ns = 'annon') {
|
||||
return types.reduce(
|
||||
(types, action) => ({
|
||||
...types,
|
||||
|
Reference in New Issue
Block a user