mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-26 13:01:43 -04:00
refactor: use top-level await in challenge tests (#61398)
Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Co-authored-by: Sem Bauke <semboot699@gmail.com>
This commit is contained in:
committed by
GitHub
parent
6e0493f918
commit
a0ba205484
@@ -36,7 +36,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Pug should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -45,35 +44,29 @@ async () => {
|
||||
'pug',
|
||||
'Your project should list "pug" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
View engine should be Pug.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/app", code);
|
||||
const res = await fetch(url);
|
||||
const app = await res.json();
|
||||
assert.equal(app?.settings?.['view engine'], "pug");
|
||||
}
|
||||
```
|
||||
|
||||
You should set the `views` property of the application to `./views/pug`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/app", code);
|
||||
const res = await fetch(url);
|
||||
const app = await res.json();
|
||||
assert.equal(app?.settings?.views, "./views/pug");
|
||||
}
|
||||
```
|
||||
|
||||
Use the correct ExpressJS method to render the index page from the response.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -82,13 +75,11 @@ async () => {
|
||||
/FCC Advanced Node and Express/gi,
|
||||
'You successfully rendered the Pug template!'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Pug should be working.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -97,6 +88,5 @@ async () => {
|
||||
/pug-success-message/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Pug should correctly render variables.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -68,6 +67,5 @@ async () => {
|
||||
/pug-variable("|')>Please log in/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Passport and Express-session should be dependencies.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -50,13 +49,11 @@ async () => {
|
||||
'express-session',
|
||||
'Your project should list "express-session" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Dependencies should be correctly required.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -70,25 +67,21 @@ async () => {
|
||||
/require.*("|')express-session("|')/,
|
||||
'You should have required express-session'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Express app should use new dependencies.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(data, /passport\.initialize/, 'Your express app should use "passport.initialize()"');
|
||||
assert.match(data, /passport\.session/, 'Your express app should use "passport.session()"');
|
||||
}
|
||||
```
|
||||
|
||||
Session and session secret should be correctly set up.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -97,6 +90,5 @@ async () => {
|
||||
/secret *:\s*process\.env(\.SESSION_SECRET|\[(?<q>"|')SESSION_SECRET\k<q>\])/,
|
||||
'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
You should serialize the user object correctly.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -67,13 +66,11 @@ async () => {
|
||||
/null,\s*user._id/gi,
|
||||
'There should be a callback in your serializeUser with (null, user._id)'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You should deserialize the user object correctly.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -87,13 +84,11 @@ async () => {
|
||||
/null,\s*null/gi,
|
||||
'There should be a callback in your deserializeUser with (null, null) for now'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
MongoDB should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -102,13 +97,11 @@ async () => {
|
||||
'mongodb',
|
||||
'Your project should list "mongodb" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Mongodb should be properly required including the ObjectId.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -122,6 +115,5 @@ async () => {
|
||||
/new ObjectID.*id/gi,
|
||||
'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Database connection should be present.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -56,13 +55,11 @@ async () => {
|
||||
/Connected to Database/gi,
|
||||
'You successfully connected to the database!'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Deserialization should now be correctly using the DB and `done(null, null)` should be called with the `doc`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -71,6 +68,5 @@ async () => {
|
||||
/null,\s*doc/gi,
|
||||
'The callback in deserializeUser of (null, null) should be altered to (null, doc)'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Passport-local should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -52,13 +51,11 @@ async () => {
|
||||
'passport-local',
|
||||
'Your project should list "passport-local " as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Passport-local should be correctly required and set up.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -77,6 +74,5 @@ async () => {
|
||||
/findOne/,
|
||||
'Your new local strategy should use the findOne query to find a username based on the inputs'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
All steps should be correctly implemented in `server.js`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -46,13 +45,11 @@ async () => {
|
||||
/login[^]*post[^]*local/,
|
||||
'You should have a route for login which accepts a POST and passport.authenticates local'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
A POST request to `/login` should correctly redirect to `/`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/login", code);
|
||||
const res = await fetch(url, { method: 'POST' });
|
||||
const data = await res.text();
|
||||
@@ -61,6 +58,5 @@ async () => {
|
||||
/Looks like this page is being rendered from Pug into HTML!/,
|
||||
'A login attempt at this point should redirect to the homepage since we do not have any registered users'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
The middleware `ensureAuthenticated` should be implemented and attached to the `/profile` route.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -54,13 +53,11 @@ async () => {
|
||||
/profile[^]*get[^]*ensureAuthenticated/,
|
||||
'Your ensureAuthenticated middleware should be attached to the /profile route'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
An unauthenticated GET request to `/profile` should correctly redirect to `/`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/profile", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -69,6 +66,5 @@ async () => {
|
||||
/Home page/,
|
||||
'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
You should correctly add a Pug render variable to `/profile`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -42,6 +41,5 @@ async () => {
|
||||
/username:( |)req.user.username/,
|
||||
'You should be passing the variable username with req.user.username into the render function of the profile page'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
`req.logout()` should be called in your `/logout` route.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -46,13 +45,11 @@ async () => {
|
||||
/req.logout/gi,
|
||||
'You should be calling req.logout() in your /logout route'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`/logout` should redirect to the home page.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/logout", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -61,6 +58,5 @@ async () => {
|
||||
/Home page/gi,
|
||||
'When a user logs out they should be redirected to the homepage'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
You should have a `/register` route and display a registration form on the home page.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -79,13 +78,11 @@ async () => {
|
||||
/register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi,
|
||||
'You should have a route that accepts a POST request on /register that queries the db with findOne and the query being username: req.body.username'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Registering should work.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const user = `freeCodeCampTester${Date.now()}`;
|
||||
const xhttp = new XMLHttpRequest();
|
||||
@@ -107,13 +104,11 @@ async () => {
|
||||
'Register should work, and redirect successfully to the profile.'
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Login should work.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const user = `freeCodeCampTester${Date.now()}`;
|
||||
const xhttpReg = new XMLHttpRequest();
|
||||
@@ -156,7 +151,6 @@ async () => {
|
||||
'The profile should properly display the welcome to the user logged in'
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Logout should work.
|
||||
|
||||
@@ -33,7 +33,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Modules should be present.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -47,6 +46,5 @@ async () => {
|
||||
/client\s*\.db[^]*routes/gi,
|
||||
'Your new modules should be called after your connection to the database'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Route `/auth/github` should be correct.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const res = await fetch(code + '/_api/routes.js');
|
||||
if (res.ok) {
|
||||
@@ -63,13 +62,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Route `/auth/github/callback` should be correct.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const res = await fetch(code + '/_api/routes.js');
|
||||
if (res.ok) {
|
||||
@@ -94,6 +91,5 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
`passport-github` dependency should be added.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -47,13 +46,11 @@ async () => {
|
||||
'passport-github',
|
||||
'Your project should list "passport-github" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`passport-github` should be required.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/auth.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -62,13 +59,11 @@ async () => {
|
||||
/require.*("|')passport-github("|')/gi,
|
||||
'You should have required passport-github'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
GitHub strategy should be setup correctly thus far.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/auth.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -92,5 +87,4 @@ async () => {
|
||||
/process\.env(\.GITHUB_CLIENT_ID|\[(?<q>"|')GITHUB_CLIENT_ID\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_ID'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,7 +50,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
GitHub strategy setup should be complete.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/auth.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -64,6 +63,5 @@ async () => {
|
||||
/GitHubStrategy[^]*cb/gi,
|
||||
'Strategy should return the callback function "cb"'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
`socket.io` should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -58,13 +57,11 @@ async () => {
|
||||
'socket.io',
|
||||
'Your project should list "socket.io" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You should correctly require and instantiate `http` as `http`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -73,13 +70,11 @@ async () => {
|
||||
/http.*=.*require.*('|")http\1/s,
|
||||
'Your project should list "http" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You should correctly require and instantiate `socket.io` as `io`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -88,13 +83,11 @@ async () => {
|
||||
/io.*=.*require.*('|")socket.io\1.*http/s,
|
||||
'You should correctly require and instantiate socket.io as io.'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Socket.IO should be listening for connections.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -103,13 +96,11 @@ async () => {
|
||||
/io.on.*('|")connection\1.*socket/s,
|
||||
'io should listen for "connection" and socket should be the 2nd arguments variable'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Your client should connect to your server.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -118,6 +109,5 @@ async () => {
|
||||
/socket.*=.*io/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
`currentUsers` should be defined.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -54,13 +53,11 @@ async () => {
|
||||
/currentUsers/s,
|
||||
'You should have variable currentUsers defined'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Server should emit the current user count at each new connection.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -69,13 +66,11 @@ async () => {
|
||||
/io.emit.*('|")user count('|").*currentUsers/s,
|
||||
'You should emit "user count" with data currentUsers'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Your client should be listening for `'user count'` event.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -84,6 +79,5 @@ async () => {
|
||||
/socket.on.*('|")user count('|")/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -29,18 +29,15 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Server should handle the event disconnect from a socket.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
assert.match(data, /socket.on.*('|")disconnect('|")/s, '');
|
||||
}
|
||||
```
|
||||
|
||||
Your client should be listening for `'user count'` event.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -49,6 +46,5 @@ async () => {
|
||||
/socket.on.*('|")user count('|")/s,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
`passport.socketio` should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -81,13 +80,11 @@ async () => {
|
||||
'passport.socketio',
|
||||
'Your project should list "passport.socketio" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`cookie-parser` should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json();
|
||||
@@ -96,13 +93,11 @@ async () => {
|
||||
'cookie-parser',
|
||||
'Your project should list "cookie-parser" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
passportSocketIo should be properly required.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -111,13 +106,11 @@ async () => {
|
||||
/require\((['"])passport\.socketio\1\)/gi,
|
||||
'You should correctly require and instantiate "passport.socketio"'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
passportSocketIo should be properly setup.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -126,6 +119,5 @@ async () => {
|
||||
/io\.use\(\s*\w+\.authorize\(/,
|
||||
'You should register "passport.socketio" as socket.io middleware and provide it correct options'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Event `'user'` should be emitted with `username`, `currentUsers`, and `connected`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -51,13 +50,11 @@ async () => {
|
||||
/io.emit.*('|")user\1.*name.*currentUsers.*connected/s,
|
||||
'You should have an event emitted named user sending name, currentUsers, and connected'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Client should properly handle and display the new data from event `'user'`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -71,6 +68,5 @@ async () => {
|
||||
/socket.on.*('|")user\1[^]*messages.*li/s,
|
||||
'You should append a list item to "#messages" on your client within the "user" event listener to announce a user came or went'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
Server should listen for `'chat message'` and then emit it properly.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -44,13 +43,11 @@ async () => {
|
||||
/socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*username.*message/s,
|
||||
'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Client should properly handle and display the new data from event `'chat message'`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/public/client.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -59,6 +56,5 @@ async () => {
|
||||
/socket.on.*('|")chat message('|")[^]*messages.*li/s,
|
||||
'You should append a list item to #messages on your client within the "chat message" event listener to display the new message'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ Submit your page when you think you've got it right. If you're running into erro
|
||||
BCrypt should be a dependency.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/package.json", code);
|
||||
const res = await fetch(url);
|
||||
const packJson = await res.json()
|
||||
@@ -40,13 +39,11 @@ async () => {
|
||||
'bcrypt',
|
||||
'Your project should list "bcrypt" as a dependency'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
BCrypt should be correctly required and implemented.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = new URL("/_api/server.js", code);
|
||||
const res = await fetch(url);
|
||||
const data = await res.text();
|
||||
@@ -65,6 +62,5 @@ async () => {
|
||||
/bcrypt.compareSync/gi,
|
||||
'You should compare the password to the hash in your strategy'
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ You should provide your own project, not the example URL.
|
||||
You can `POST` to `/api/users` with form data `username` to create a new user.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -79,13 +78,11 @@ async () => {
|
||||
if(!res.ok) {
|
||||
throw new Error(`${res.status} ${res.statusText}`)
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The returned response from `POST /api/users` with form data `username` will be an object with `username` and `_id` properties.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -99,26 +96,22 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can make a `GET` request to `/api/users` to get a list of all users.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users');
|
||||
assert.isTrue(res.ok);
|
||||
if(!res.ok) {
|
||||
throw new Error(`${res.status} ${res.statusText}`)
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The `GET` request to `/api/users` returns an array.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users');
|
||||
if(res.ok){
|
||||
@@ -127,13 +120,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Each element in the array returned from `GET /api/users` is an object literal containing a user's `username` and `_id`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users');
|
||||
if(res.ok){
|
||||
@@ -147,13 +138,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
You can `POST` to `/api/users/:_id/exercises` with form data `description`, `duration`, and optionally `date`. If no date is supplied, the current date will be used.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -181,13 +170,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The response returned from `POST /api/users/:_id/exercises` will be the user object with the exercise fields added.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -234,13 +221,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can make a `GET` request to `/api/users/:_id/logs` to retrieve a full exercise log of any user.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -273,13 +258,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
A request to a user's log `GET /api/users/:_id/logs` returns a user object with a `count` property representing the number of exercises that belong to that user.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -314,13 +297,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
A `GET` request to `/api/users/:_id/logs` will return the user object with a `log` array of all the exercises added.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -358,13 +339,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`)
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Each item in the `log` array that is returned from `GET /api/users/:_id/logs` is an object that should have a `description`, `duration`, and `date` properties.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + `/api/users`, {
|
||||
method: 'POST',
|
||||
@@ -405,13 +384,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`)
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The `description` property of any object in the `log` array that is returned from `GET /api/users/:_id/logs` should be a string.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -452,13 +429,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The `duration` property of any object in the `log` array that is returned from `GET /api/users/:_id/logs` should be a number.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -499,13 +474,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The `date` property of any object in the `log` array that is returned from `GET /api/users/:_id/logs` should be a string. Use the `dateString` format of the `Date` API.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -561,13 +534,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
You can add `from`, `to` and `limit` parameters to a `GET /api/users/:_id/logs` request to retrieve part of the log of any user. `from` and `to` are dates in `yyyy-mm-dd` format. `limit` is an integer of how many logs to send back.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/users', {
|
||||
method: 'POST',
|
||||
@@ -630,5 +601,4 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -32,7 +32,6 @@ You should provide your own project, not the example URL.
|
||||
You can POST a URL to `/api/shorturl` and get a JSON response with `original_url` and `short_url` properties. Here's an example: `{ original_url : 'https://freeCodeCamp.org', short_url : 1}`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const urlVariable = Date.now();
|
||||
const fullUrl = `${url}/?v=${urlVariable}`
|
||||
@@ -48,13 +47,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When you visit `/api/shorturl/<short_url>`, you will be redirected to the original URL.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const urlVariable = Date.now();
|
||||
const fullUrl = `${url}/?v=${urlVariable}`
|
||||
@@ -80,13 +77,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${getResponse.status} ${getResponse.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If you pass an invalid URL that doesn't follow the valid `http://www.example.com` format, the JSON response will contain `{ error: 'invalid url' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/shorturl', {
|
||||
method: 'POST',
|
||||
@@ -100,6 +95,5 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -32,29 +32,24 @@ You should provide your own project, not the example URL.
|
||||
You can submit a form that includes a file upload.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const site = await fetch(code);
|
||||
const data = await site.text();
|
||||
const doc = new DOMParser().parseFromString(data, 'text/html');
|
||||
assert(doc.querySelector('input[type="file"]'));
|
||||
};
|
||||
```
|
||||
|
||||
The form file input field has the `name` attribute set to `upfile`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const site = await fetch(code);
|
||||
const data = await site.text();
|
||||
const doc = new DOMParser().parseFromString(data, 'text/html');
|
||||
assert(doc.querySelector('input[name="upfile"]'));
|
||||
};
|
||||
```
|
||||
|
||||
When you submit a file, you receive the file `name`, `type`, and `size` in bytes within the JSON response.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const formData = new FormData();
|
||||
const fileData = await fetch(
|
||||
'https://cdn.freecodecamp.org/weather-icons/01d.png'
|
||||
@@ -69,6 +64,5 @@ async () => {
|
||||
assert.property(parsed, 'size');
|
||||
assert.equal(parsed.name, 'icon');
|
||||
assert.equal(parsed.type, 'image/png');
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ assert.isTrue(cssCheck.length > 0 || htmlSourceAttr.length > 0);
|
||||
Your `#navbar` element should always be at the top of the viewport.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
const timeout = milliseconds =>
|
||||
new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
|
||||
@@ -144,7 +143,6 @@ Your `#navbar` element should always be at the top of the viewport.
|
||||
'Navbar should be at the top of the viewport even after ' + 'scrolling '
|
||||
);
|
||||
window.scroll(0, 0);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -206,7 +206,6 @@ assert(!!el && el.name === 'email')
|
||||
Your `#nav-bar` should always be at the top of the viewport.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
const timeout = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
|
||||
const header = document.getElementById('header');
|
||||
@@ -240,7 +239,6 @@ Your `#nav-bar` should always be at the top of the viewport.
|
||||
);
|
||||
|
||||
window.scroll(0, 0);
|
||||
})();
|
||||
```
|
||||
|
||||
Your Product Landing Page should use at least one media query.
|
||||
|
||||
@@ -138,7 +138,6 @@ assert.exists(el);
|
||||
When the `#search-input` element contains the value `Red` and the `#search-button` element is clicked, an alert should appear with the text `"Creature not found"`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -160,13 +159,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `Pyrolynx` and the `#search-button` element is clicked, the values in the `#creature-name`, `#creature-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `PYROLYNX`, `#1` or `1`, `Weight: 42` or `42`, `Height: 32` or `32`, `65`, `80`, `50`, `90`, `55`, and `100`, respectively.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -206,13 +203,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `Pyrolynx` and the `#search-button` element is clicked, a single element should be added within the `#types` element that contains the text `FIRE`. The `#types` element content should be cleared between searches.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -235,13 +230,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `2` and the `#search-button` element is clicked, the values in the `#creature-name`, `#creature-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `AQUOROC`, `#2` or `2`, `Weight: 220` or `220`, `Height: 53` or `53`, `85`, `90`, `120`, `60`, `70`, and `40`, respectively.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -281,13 +274,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `2` and the `#search-button` element is clicked, two elements should be added within the `#types` element that contain text values `WATER` and `ROCK`, respectively. The `#types` element content should be cleared between searches.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -311,13 +302,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains an invalid creature name and the `#search-button` element is clicked, an alert should appear with the text `"Creature not found"`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -342,14 +331,12 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
When the `#search-input` element contains a valid creature ID and the `#search-button` element is clicked, the UI should be filled with the correct data.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -379,13 +366,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the search button is clicked, the app should send a fetch request to the correct endpoint for the creature name or ID.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const spy = __helpers.spyOn(window, 'fetch');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
@@ -411,7 +396,6 @@ async () => {
|
||||
assert.strictEqual(calls[0].toLowerCase(), 'https://rpg-creature-api.freecodecamp.rocks/api/creature/pyrolynx');
|
||||
assert.strictEqual(calls[1], 'https://rpg-creature-api.freecodecamp.rocks/api/creature/2');
|
||||
assert.strictEqual(calls[2], `https://rpg-creature-api.freecodecamp.rocks/api/creature/${randomValidCreatureId}`);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -47,7 +47,6 @@ You can provide your own project, not the example URL.
|
||||
You should set the content security policies to only allow loading of scripts and CSS from your server.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.isTrue(
|
||||
@@ -56,25 +55,21 @@ async () => {
|
||||
assert.isTrue(
|
||||
parsed.headers['content-security-policy'].includes("style-src 'self'")
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `GET` request to `/api/stock-prices`, passing a NASDAQ stock symbol to a `stock` query parameter. The returned object will contain a property named `stockData`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(
|
||||
code + '/api/stock-prices?stock=GOOG'
|
||||
);
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'stockData');
|
||||
};
|
||||
```
|
||||
|
||||
The `stockData` property includes the `stock` symbol as a string, the `price` as a number, and `likes` as a number.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(
|
||||
code + '/api/stock-prices?stock=GOOG'
|
||||
);
|
||||
@@ -83,7 +78,6 @@ async () => {
|
||||
assert.typeOf(ticker.price, 'number');
|
||||
assert.typeOf(ticker.likes, 'number');
|
||||
assert.typeOf(ticker.stock, 'string');
|
||||
};
|
||||
```
|
||||
|
||||
You can also pass along a `like` field as `true` (boolean) to have your like added to the stock(s). Only 1 like per IP should be accepted.
|
||||
@@ -95,7 +89,6 @@ You can also pass along a `like` field as `true` (boolean) to have your like add
|
||||
If you pass along 2 stocks, the returned value will be an array with information about both stocks. Instead of `likes`, it will display `rel_likes` (the difference between the likes on both stocks) for both `stockData` objects.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(
|
||||
code + '/api/stock-prices?stock=GOOG&stock=MSFT'
|
||||
);
|
||||
@@ -104,19 +97,16 @@ async () => {
|
||||
assert.typeOf(ticker, 'array');
|
||||
assert.property(ticker[0], 'rel_likes');
|
||||
assert.property(ticker[1], 'rel_likes');
|
||||
};
|
||||
```
|
||||
|
||||
All 5 functional tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const tests = await fetch(code + '/_api/get-tests');
|
||||
const parsed = await tests.json();
|
||||
assert.isTrue(parsed.length >= 5);
|
||||
parsed.forEach((test) => {
|
||||
assert.equal(test.state, 'passed');
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -49,37 +49,30 @@ You can provide your own project, not the example URL.
|
||||
Only allow your site to be loaded in an iFrame on your own pages.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.isTrue(parsed.headers['x-frame-options']?.includes('SAMEORIGIN'));
|
||||
};
|
||||
```
|
||||
|
||||
Do not allow DNS prefetching.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.isTrue(parsed.headers['x-dns-prefetch-control']?.includes('off'));
|
||||
};
|
||||
```
|
||||
|
||||
Only allow your site to send the referrer for your own pages.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.isTrue(parsed.headers['referrer-policy']?.includes('same-origin'));
|
||||
};
|
||||
```
|
||||
|
||||
You can send a POST request to `/api/threads/{board}` with form data including `text` and `delete_password`. The saved database record will have at least the fields `_id`, `text`, `created_on`(date & time), `bumped_on`(date & time, starts same as `created_on`), `reported` (boolean), `delete_password`, & `replies` (array).
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const date = new Date();
|
||||
const text = `fcc_test_${date}`;
|
||||
const deletePassword = 'delete_me';
|
||||
@@ -105,13 +98,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a POST request to `/api/replies/{board}` with form data including `text`, `delete_password`, & `thread_id`. This will update the `bumped_on` date to the comment's date. In the thread's `replies` array, an object will be saved with at least the properties `_id`, `text`, `created_on`, `delete_password`, & `reported`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const body = await fetch(url + '/api/threads/fcc_test');
|
||||
const thread = await body.json();
|
||||
@@ -142,13 +133,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a GET request to `/api/threads/{board}`. Returned will be an array of the most recent 10 bumped threads on the board with only the most recent 3 replies for each. The `reported` and `delete_password` fields will not be sent to the client.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
const res = await fetch(url + '/api/threads/fcc_test');
|
||||
|
||||
@@ -173,13 +162,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a GET request to `/api/replies/{board}?thread_id={thread_id}`. Returned will be the entire thread with all its replies, also excluding the same fields from the client as the previous test.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
let res = await fetch(url + '/api/threads/fcc_test');
|
||||
const threads = await res.json();
|
||||
@@ -205,13 +192,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a DELETE request to `/api/threads/{board}` and pass along the `thread_id` & `delete_password` to delete the thread. Returned will be the string `incorrect password` or `success`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
let res = await fetch(url + '/api/threads/fcc_test');
|
||||
const threads = await res.json();
|
||||
@@ -242,13 +227,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a DELETE request to `/api/replies/{board}` and pass along the `thread_id`, `reply_id`, & `delete_password`. Returned will be the string `incorrect password` or `success`. On success, the text of the `reply_id` will be changed to `[deleted]`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
|
||||
const thread_data = {
|
||||
@@ -297,13 +280,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a PUT request to `/api/threads/{board}` and pass along the `thread_id`. Returned will be the string `reported`. The `reported` value of the `thread_id` will be changed to `true`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
|
||||
let res = await fetch(`${url}/api/threads/fcc_test`);
|
||||
@@ -328,13 +309,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a PUT request to `/api/replies/{board}` and pass along the `thread_id` & `reply_id`. Returned will be the string `reported`. The `reported` value of the `reply_id` will be changed to `true`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const url = code;
|
||||
|
||||
let res = await fetch(`${url}/api/threads/fcc_test`);
|
||||
@@ -360,13 +339,11 @@ async () => {
|
||||
} else {
|
||||
throw new Error(`${res.status} ${res.statusText}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 10 functional tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const tests = await fetch(code + '/_api/get-tests');
|
||||
const parsed = await tests.json();
|
||||
assert.isTrue(parsed.length >= 10);
|
||||
@@ -374,6 +351,5 @@ async () => {
|
||||
assert.equal(test.state, 'passed');
|
||||
assert.isAtLeast(test.assertions.length, 1);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -127,27 +127,22 @@ Players can disconnect from the game at any time.
|
||||
Prevent the client from trying to guess / sniff the MIME type.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.equal(parsed.headers['x-content-type-options'], 'nosniff');
|
||||
};
|
||||
```
|
||||
|
||||
Prevent cross-site scripting (XSS) attacks.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.equal(parsed.headers['x-xss-protection'], '1; mode=block');
|
||||
};
|
||||
```
|
||||
|
||||
Nothing from the website is cached in the client.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.equal(parsed.headers['surrogate-control'], 'no-store');
|
||||
@@ -157,15 +152,12 @@ async () => {
|
||||
);
|
||||
assert.equal(parsed.headers['pragma'], 'no-cache');
|
||||
assert.equal(parsed.headers['expires'], '0');
|
||||
};
|
||||
```
|
||||
|
||||
The headers say that the site is powered by "PHP 7.4.3" even though it isn't (as a security measure).
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const data = await fetch(code + '/_api/app-info');
|
||||
const parsed = await data.json();
|
||||
assert.equal(parsed.headers['x-powered-by'], 'PHP 7.4.3');
|
||||
};
|
||||
```
|
||||
|
||||
@@ -44,7 +44,7 @@ Your code should use the fetched data to replace the inner HTML
|
||||
const catData = 'dummy data';
|
||||
const ref = fetch;
|
||||
fetch = () => Promise.resolve({ json: () => catData });
|
||||
async () => {
|
||||
|
||||
try {
|
||||
document.getElementById('getMessage').click();
|
||||
await new Promise((resolve, reject) => setTimeout(() => resolve(), 250));
|
||||
@@ -57,7 +57,7 @@ async () => {
|
||||
JSON.stringify(catData)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
Your code should make a `GET` request with `fetch`.
|
||||
|
||||
@@ -24,27 +24,23 @@ demoType: onClick
|
||||
You should define and export a `ColorPicker` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
|
||||
assert.property(exports, "ColorPicker");
|
||||
}
|
||||
```
|
||||
|
||||
You should use the `useState` hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.ColorPicker);
|
||||
assert.isAtLeast(abuseState.calls.length, 1);
|
||||
}
|
||||
```
|
||||
|
||||
You should use the return value of the `useState` hook.
|
||||
@@ -62,14 +58,12 @@ assert.match(code, /\[.*,.*\]\s*=\s*(?:React\.)?useState\(/);
|
||||
You should pass `#ffffff` as the initial value to your `useState` call.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.ColorPicker);
|
||||
assert.match(abuseState.calls[0], /#ffffff/i);
|
||||
}
|
||||
```
|
||||
|
||||
You should render an element with `color-picker-container` as the `id`.
|
||||
@@ -141,7 +135,6 @@ assert.match(code, regex);
|
||||
When your `#color-input` value is changed, your `#color-picker-container` should change its background color to reflect that value.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const inputForSure = document.getElementById("color-input");
|
||||
assert.exists(inputForSure);
|
||||
const nativeValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
|
||||
@@ -153,7 +146,6 @@ async () => {
|
||||
assert.exists(container);
|
||||
const style = window.getComputedStyle(container);
|
||||
assert.equal(style?.backgroundColor, "rgb(0, 0, 1)");
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -42,14 +42,12 @@ demoType: onClick
|
||||
You should export a `CurrencyConverter` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
|
||||
const exports = {};
|
||||
const a = eval(script);
|
||||
|
||||
assert.property(exports, "CurrencyConverter");
|
||||
}
|
||||
```
|
||||
|
||||
You should have one `input[type="number"]` element to accept the amount to be converted from.
|
||||
@@ -82,7 +80,6 @@ for (const select of selects) {
|
||||
Changing the value of the first `select` element should cause the converted amounts to be recalculated.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
function spyOn(
|
||||
obj,
|
||||
method
|
||||
@@ -116,13 +113,11 @@ async () => {
|
||||
first[Object.keys(first).find((k) => k.startsWith("__reactProps"))].onChange({...ev, target: first});
|
||||
});
|
||||
assert.equal(abuseMemo.calls.length, 2);
|
||||
}
|
||||
```
|
||||
|
||||
Changing the value of the second `select` element should not cause the converted amounts to be recalculated.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
function spyOn(
|
||||
obj,
|
||||
method
|
||||
@@ -156,13 +151,11 @@ async () => {
|
||||
second[Object.keys(second).find((k) => k.startsWith("__reactProps"))].onChange({...ev, target: second});
|
||||
});
|
||||
assert.equal(abuseMemo.calls.length, 1);
|
||||
}
|
||||
```
|
||||
|
||||
Changing the value of the first `select` element should cause a textual change on the page.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const s = await __helpers.prepTestComponent(window.index.CurrencyConverter);
|
||||
|
||||
const nonFormContentBefore = getInnerTextExcept(s, "input,select");
|
||||
@@ -185,13 +178,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Changing the value of the second `select` element should cause a textual change on the page.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const s = await __helpers.prepTestComponent(window.index.CurrencyConverter);
|
||||
|
||||
const nonFormContentBefore = getInnerTextExcept(s, "input,select");
|
||||
@@ -214,13 +205,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The converted amount should be displayed in the format `XX.XX CCC`, where `XX.XX` is the converted amount rounded to two decimal places and `CCC` is the currency code.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const s = await __helpers.prepTestComponent(window.index.CurrencyConverter);
|
||||
|
||||
const inp = s.querySelector('input[type="number"]');
|
||||
@@ -248,13 +237,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The converted amount should be different from the input amount.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const s = await __helpers.prepTestComponent(window.index.CurrencyConverter);
|
||||
|
||||
const inp = s.querySelector('input[type="number"]');
|
||||
@@ -292,7 +279,6 @@ async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --before-all--
|
||||
|
||||
@@ -130,7 +130,6 @@ try {
|
||||
Changing the value of the `input[type="text"]` elements should update component state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const abuseState = __helpers.spyOn(React, 'useState');
|
||||
const script = [...document.querySelectorAll('script')]
|
||||
@@ -174,13 +173,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Changing the value of the `input[type="email"]` elements should update component state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const abuseState = __helpers.spyOn(React, 'useState');
|
||||
const script = [...document.querySelectorAll('script')]
|
||||
@@ -224,13 +221,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Changing the value of the `input[type="number"]` elements should update component state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const abuseState = __helpers.spyOn(React, 'useState');
|
||||
const script = [...document.querySelectorAll('script')]
|
||||
@@ -274,13 +269,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Changing the value of the `input[type="checkbox"]` elements should update component state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const abuseState = __helpers.spyOn(React, 'useState');
|
||||
const script = [...document.querySelectorAll('script')]
|
||||
@@ -324,13 +317,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Submitting the form should not result in the page reloading.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
// Ideally, `window.onload` would be watched. However, the frame runner disables submissions causing window reloads.
|
||||
// So, the test needs to manually check for the `.preventDefault` call.
|
||||
@@ -361,13 +352,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After submission, there should be an element on the page indicating the state of the name `input`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const inp = document.querySelector(`input[type="text"]`);
|
||||
assert.exists(inp);
|
||||
@@ -390,13 +379,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After submission, there should be an element on the page indicating the state of the email `input`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const inp = document.querySelector(`input[type="email"]`);
|
||||
assert.exists(inp);
|
||||
@@ -419,13 +406,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After submission, there should be an element on the page indicating the state of the number of attendees `input`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const inp = document.querySelector(`input[type="number"]`);
|
||||
assert.exists(inp);
|
||||
@@ -448,13 +433,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After submission, there should be an element on the page indicating the state of the dietary preferences `input`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const inp = document.querySelector(`input[type="text"]:not(:required)`);
|
||||
assert.exists(inp);
|
||||
@@ -477,13 +460,11 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After submission, there should be an element on the page indicating the state of the additional guests `input`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let inp = document.querySelector(`input[type="checkbox"]`);
|
||||
assert.exists(inp);
|
||||
@@ -522,7 +503,6 @@ async () => {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --before-all--
|
||||
|
||||
@@ -55,35 +55,29 @@ assert.equal(item.tagName, 'DIV');
|
||||
The background color of the `.mood-board-item` element should be set to the value of `color` prop using inline styles.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const container = await __helpers.prepTestComponent(window.index.MoodBoardItem, { color: "red" });
|
||||
const moodBoardItem = container.querySelector(".mood-board-item");
|
||||
assert.equal(moodBoardItem.style.backgroundColor, "red");
|
||||
}
|
||||
```
|
||||
|
||||
Your `MoodBoardItem` component should render an `img` element with a class of `mood-board-image` and its `src` set to the value of the `image` prop.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const container = await __helpers.prepTestComponent(window.index.MoodBoardItem, { image: "https://cdn.freecodecamp.org/curriculum/labs/pathway.jpg" });
|
||||
const img = container.querySelector(".mood-board-image");
|
||||
assert.exists(img);
|
||||
assert.equal(img.tagName, "IMG");
|
||||
assert.equal(img.src, "https://cdn.freecodecamp.org/curriculum/labs/pathway.jpg");
|
||||
}
|
||||
```
|
||||
|
||||
Your `MoodBoardItem` component should render an `h3` element with a class of `mood-board-text` and its text set to the value of the `description` prop.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const container = await __helpers.prepTestComponent(window.index.MoodBoardItem, { description: "Caribbean" });
|
||||
const text = container.querySelector(".mood-board-text");
|
||||
assert.exists(text);
|
||||
assert.equal(text.tagName, "H3");
|
||||
assert.equal(text.textContent, "Caribbean");
|
||||
}
|
||||
```
|
||||
|
||||
You should export a `MoodBoard` component.
|
||||
|
||||
@@ -114,7 +114,6 @@ assert.strictEqual(button, 'Generate OTP');
|
||||
When the button is clicked, it should display a new OTP in the `h2` element with id `otp-display`.
|
||||
|
||||
```js
|
||||
async() => {
|
||||
try {
|
||||
// allow the page to render
|
||||
clock.tick(1)
|
||||
@@ -130,13 +129,11 @@ assert.match(h2.textContent?.trim(), /[0-9]/, "OTP should be a number");
|
||||
// we need to wait for the countdown to finish for the next test to work otherwise the button will be disabled
|
||||
await clock.runAllAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated OTP should be 6 digits long.
|
||||
|
||||
```js
|
||||
async() => {
|
||||
try {
|
||||
const button = document.querySelector(".container button#generate-otp-button");
|
||||
button.click();
|
||||
@@ -149,14 +146,12 @@ assert.match(h2.textContent.trim(), /^[0-9]{6}$/, "OTP should be a 6-digit numbe
|
||||
// we need to wait for the countdown to finish for the next test to work otherwise the button will be disabled
|
||||
await clock.runAllAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
When the button is clicked, the `p` element with id of `otp-timer` should show a 5-second countdown.
|
||||
|
||||
```js
|
||||
async() => {
|
||||
try {
|
||||
// allow the page to render
|
||||
clock.tick(1)
|
||||
@@ -173,13 +168,11 @@ assert.match(p.textContent, /Expires in: 5 seconds/, "Countdown should start at
|
||||
// we need to wait for the countdown to finish for the next test to work otherwise the button will be disabled
|
||||
await clock.runAllAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The message in the `p` element with id `otp-timer` should update every second to show the remaining time.
|
||||
|
||||
```js
|
||||
async() => {
|
||||
const button = document.querySelector(".container button#generate-otp-button");
|
||||
button.click();
|
||||
clock.tick(1)
|
||||
@@ -188,13 +181,11 @@ assert.match(p.textContent, /Expires in: 5 seconds/);
|
||||
await clock.tickAsync(1000)
|
||||
p = document.querySelector(".container p#otp-timer");
|
||||
assert.match(p.textContent, /Expires in: 4 seconds/, "Countdown should update to 4 seconds after 1 second");
|
||||
}
|
||||
```
|
||||
|
||||
The `"Generate OTP"` button should be disabled while the countdown timer is running.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const button = document.querySelector(".container button#generate-otp-button");
|
||||
button.click();
|
||||
@@ -204,26 +195,22 @@ try {
|
||||
} finally {
|
||||
await clock.runAllAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `"Generate OTP"` button should be enabled once the countdown timer reaches 0.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
|
||||
const button = document.querySelector(".container button#generate-otp-button");
|
||||
button.click();
|
||||
await clock.tickAsync(6000);
|
||||
|
||||
assert.isFalse(button.disabled);
|
||||
}
|
||||
```
|
||||
|
||||
When the countdown timer reaches `0`, you should display the message `OTP expired. Click the button to generate a new OTP.`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const button = document.querySelector(".container button#generate-otp-button");
|
||||
button.click();
|
||||
|
||||
@@ -231,7 +218,6 @@ async () => {
|
||||
|
||||
const p = document.querySelector(".container p#otp-timer");
|
||||
assert.equal(p.textContent.trim(), "OTP expired. Click the button to generate a new OTP.");
|
||||
}
|
||||
```
|
||||
|
||||
You should export the `OTPGenerator` component.
|
||||
|
||||
@@ -121,7 +121,6 @@ assert.isTrue(cssCheck.length > 0 || htmlSourceAttr.length > 0);
|
||||
Your `#navbar` element should always be at the top of the viewport.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
const timeout = milliseconds =>
|
||||
new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
|
||||
@@ -145,7 +144,6 @@ Your `#navbar` element should always be at the top of the viewport.
|
||||
'Navbar should be at the top of the viewport even after ' + 'scrolling '
|
||||
);
|
||||
window.scroll(0, 0);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -221,7 +221,6 @@ assert.strictEqual(el.name, 'email');
|
||||
Your `#nav-bar` should always be at the top of the viewport.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
const timeout = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
|
||||
const header = document.getElementById('header');
|
||||
@@ -255,7 +254,6 @@ Your `#nav-bar` should always be at the top of the viewport.
|
||||
);
|
||||
|
||||
window.scroll(0, 0);
|
||||
})();
|
||||
```
|
||||
|
||||
Your Product Landing Page should use at least one media query.
|
||||
|
||||
@@ -54,14 +54,12 @@ clock.uninstall();
|
||||
You should export a `Board` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
|
||||
const exports = {};
|
||||
const a = eval(script);
|
||||
|
||||
assert.property(exports, "Board");
|
||||
}
|
||||
```
|
||||
|
||||
You should have nine `button.square` elements.
|
||||
@@ -126,7 +124,6 @@ try {
|
||||
The first click of a `button.square` element should result in `X` being displayed within the element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const el = document.querySelector("button.square");
|
||||
el.click();
|
||||
|
||||
@@ -138,13 +135,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking on the `button#reset` element should reset the game.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
// NOTE: This test is intentionally high-up, because the latter tests rely on the functionality.
|
||||
const el = document.querySelector("button.square");
|
||||
el.click();
|
||||
@@ -157,13 +152,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The second click of a `button.square` element should result in `O` being displayed within the element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
await reset(assert);
|
||||
const els = document.querySelectorAll("button.square");
|
||||
els[0].click();
|
||||
@@ -178,13 +171,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All subsequent clicks of a `button.square` element should alternate between displaying `X` and `O` within the element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
await reset(assert);
|
||||
const els = document.querySelectorAll("button.square");
|
||||
try {
|
||||
@@ -198,13 +189,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking on an already used `button.square` element should result in no change.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
await reset(assert);
|
||||
const els = document.querySelectorAll("button.square");
|
||||
try {
|
||||
@@ -221,13 +210,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking on a `button.square` element after the game has ended should result in no change.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
await reset(assert);
|
||||
const els = document.querySelectorAll("button.square");
|
||||
try {
|
||||
@@ -251,13 +238,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The game should display a message indicating the winner to be `X` or `O`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
// Get to almost winning state
|
||||
// Check dom
|
||||
// Click winning square
|
||||
@@ -336,13 +321,11 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The game should display a message indicating a draw.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
// Get to almost draw state
|
||||
// Check dom
|
||||
// Click final square
|
||||
@@ -376,15 +359,12 @@ async () => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Uninstall the clock, ignore this test
|
||||
|
||||
```js
|
||||
async () => {
|
||||
clock.uninstall()
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -344,89 +344,73 @@ Inside the `select` element there should be 6 `option` elements, one for each of
|
||||
You should have an `img` element with the id `weather-icon` for displaying the weather icon.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'chicago';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('img#weather-icon'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `main-temperature` for displaying the main temperature.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'london';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#main-temperature'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `feels-like` for displaying what the temperature feels like.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'london';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#feels-like'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `humidity` for displaying the amount of humidity in air.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'london';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#humidity'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `wind` for displaying the wind speed.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'new york';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#wind'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `wind-gust` for displaying the wind gust.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'new york';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#wind-gust'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `weather-main` for displaying the main weather type.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'new york';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#weather-main'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have an element with the id `location` for displaying the current location.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
document.querySelector('select').value = 'new york';
|
||||
document.querySelector('#get-weather-btn').click();
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
assert.exists(document.querySelector('#location'));
|
||||
};
|
||||
```
|
||||
|
||||
You should have a `showWeather` function.
|
||||
@@ -444,7 +428,6 @@ assert.isFunction(getWeather);
|
||||
The `getWeather` function should accept a city as its only argument and return the JSON from the Weather API.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const city = 'chicago';
|
||||
const url = `https://weather-proxy.freecodecamp.rocks/api/city/${city}`;
|
||||
@@ -454,13 +437,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `showWeather` function should call the `getWeather` function to get the weather data.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let flag = false;
|
||||
const temp = getWeather;
|
||||
@@ -476,13 +457,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `showWeather` function should manage the case in which `getWeather` returns `undefined`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const temp = getWeather;
|
||||
try {
|
||||
getWeather = () => {};
|
||||
@@ -496,13 +475,11 @@ async () => {
|
||||
} finally {
|
||||
getWeather = temp;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When New York is selected the `showWeather` function should display the data from the API in the respective HTML elements. If the value from the API is `undefined`, you should show `N/A`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const city = 'new york';
|
||||
document.querySelector('select').value = city;
|
||||
@@ -533,13 +510,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When Chicago is selected the `showWeather` function should display the data from the API in the respective HTML elements. If the value from the API is `undefined`, you should show `N/A`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const city = 'chicago';
|
||||
document.querySelector('select').value = city;
|
||||
@@ -570,13 +545,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When London is selected the `showWeather` function should display the data from the API in the respective HTML elements. If the value from the API is `undefined`, you should show `N/A`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const city = 'london';
|
||||
document.querySelector('select').value = city;
|
||||
@@ -607,13 +580,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When Tokyo is selected the `showWeather` function should display the data from the API in the respective HTML elements. If the value from the API is `undefined`, you should show `N/A`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const city = 'tokyo';
|
||||
document.querySelector('select').value = city;
|
||||
@@ -644,13 +615,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When Los Angeles is selected the `showWeather` function should display the data from the API in the respective HTML elements. If the value from the API is `undefined`, you should show `N/A`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const city = 'los angeles';
|
||||
document.querySelector('select').value = city;
|
||||
@@ -681,7 +650,6 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If there is an error, your `getWeather` function should log the error to the console.
|
||||
|
||||
@@ -32,7 +32,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('img'), "'img' element not found");
|
||||
await retryingTest(() => document.querySelector('img'), "'img' element not found");
|
||||
```
|
||||
|
||||
Your `img` element should have the class `"user-img"`.
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('p'), "'p' element not found");
|
||||
await retryingTest(() => document.querySelector('p'), "'p' element not found");
|
||||
```
|
||||
|
||||
Your `p` element should have the class `"bio"`
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('a'), "'a' element not found");
|
||||
await retryingTest(() => document.querySelector('a'), "'a' element not found");
|
||||
```
|
||||
|
||||
Your anchor element should have the class `"author-link"`.
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('p')?.previousElementSibling?.tagName === 'DIV', "'div' element not found");
|
||||
await retryingTest(() => document.querySelector('p')?.previousElementSibling?.tagName === 'DIV', "'div' element not found");
|
||||
```
|
||||
|
||||
Your `div` element should have the `class` set to `"purple-divider"`.
|
||||
|
||||
@@ -71,7 +71,6 @@ You can `GET` `/api/convert` with a single parameter containing an accepted numb
|
||||
You can convert `'gal'` to `'L'` and vice versa. (1 gal to 3.78541 L)
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data1 = await $.get(code + '/api/convert?input=1gal');
|
||||
assert.equal(data1.returnNum, 3.78541);
|
||||
@@ -88,13 +87,11 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can convert `'lbs'` to `'kg'` and vice versa. (1 lbs to 0.453592 kg)
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data1 = await $.get(code + '/api/convert?input=1lbs');
|
||||
assert.equal(data1.returnNum, 0.45359);
|
||||
@@ -111,13 +108,11 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can convert `'mi'` to `'km'` and vice versa. (1 mi to 1.60934 km)
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data1 = await $.get(code + '/api/convert?input=1mi');
|
||||
assert.equal(data1.returnNum, 1.60934);
|
||||
@@ -134,13 +129,11 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All incoming units should be accepted in both upper and lower case, but should be returned in both the `initUnit` and `returnUnit` in lower case, except for liter, which should be represented as an uppercase `'L'`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data1 = await $.get(code + '/api/convert?input=1gal');
|
||||
assert.equal(data1.initUnit, 'gal');
|
||||
@@ -157,26 +150,22 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If the unit of measurement is invalid, returned will be `'invalid unit'`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data = await $.get(code + '/api/convert?input=1min');
|
||||
assert(data.error === 'invalid unit' || data === 'invalid unit');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If the number is invalid, returned will be `'invalid number'`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data = await $.get(
|
||||
code + '/api/convert?input=1//2gal'
|
||||
@@ -185,13 +174,11 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If both the unit and number are invalid, returned will be `'invalid number and unit'`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data = await $.get(
|
||||
code + '/api/convert?input=1//2min'
|
||||
@@ -203,13 +190,11 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can use fractions, decimals or both in the parameter (ie. 5, 1/2, 2.5/6), but if nothing is provided it will default to 1.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data1 = await $.get(code + '/api/convert?input=mi');
|
||||
assert.approximately(data1.initNum, 1, 0.001);
|
||||
@@ -234,13 +219,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Your return will consist of the `initNum`, `initUnit`, `returnNum`, `returnUnit`, and `string` spelling out units in the format `'{initNum} {initUnitString} converts to {returnNum} {returnUnitString}'` with the result rounded to 5 decimals.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const data = await $.get(code + '/api/convert?input=2mi');
|
||||
assert.equal(data.initNum, 2);
|
||||
@@ -251,13 +234,11 @@ async () => {
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 16 unit tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -276,13 +257,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 5 functional tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -301,6 +280,5 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ You can provide your own project, not the example URL.
|
||||
You can send a `POST` request to `/api/issues/{projectname}` with form data containing the required fields `issue_title`, `issue_text`, `created_by`, and optionally `assigned_to` and `status_text`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let test_data = {
|
||||
issue_title: 'Faux Issue Title',
|
||||
@@ -65,13 +64,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `POST` request to `/api/issues/{projectname}` will return the created object, and must include all of the submitted fields. Excluded optional fields will be returned as empty strings. Additionally, include `created_on` (date/time), `updated_on` (date/time), `open` (boolean, `true` for open - default value, `false` for closed), and `_id`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let test_data = {
|
||||
issue_title: 'Faux Issue Title 2',
|
||||
@@ -99,13 +96,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If you send a `POST` request to `/api/issues/{projectname}` without the required fields, returned will be the error `{ error: 'required field(s) missing' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let test_data = { created_by: 'fCC' };
|
||||
const data = await $.post(code + '/api/issues/fcc-project', {
|
||||
@@ -117,13 +112,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `GET` request to `/api/issues/{projectname}` for an array of all issues for that specific `projectname`, with all the fields present for each issue.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let test_data = { issue_text: 'Get Issues Test', created_by: 'fCC' };
|
||||
const url =
|
||||
@@ -164,13 +157,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `GET` request to `/api/issues/{projectname}` and filter the request by also passing along any field and value as a URL query (ie. `/api/issues/{project}?open=false`). You can pass one or more field/value pairs at once.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let test_data = {
|
||||
issue_title: 'To be Filtered',
|
||||
@@ -210,13 +201,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `PUT` request to `/api/issues/{projectname}` with an `_id` and one or more fields to update. On success, the `updated_on` field should be updated, and returned should be `{ result: 'successfully updated', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let initialData = {
|
||||
issue_title: 'Issue to be Updated',
|
||||
@@ -245,13 +234,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `PUT` request sent to `/api/issues/{projectname}` does not include an `_id`, the return value is `{ error: 'missing _id' }`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const url = code + '/api/issues/fcc-project';
|
||||
const badUpdate = await $.ajax({ url: url, type: 'PUT' });
|
||||
@@ -261,13 +248,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `PUT` request sent to `/api/issues/{projectname}` does not include update fields, the return value is `{ error: 'no update field(s) sent', '_id': _id }`. On any other error, the return value is `{ error: 'could not update', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const url = code + '/api/issues/fcc-project';
|
||||
const badUpdate = await $.ajax({
|
||||
@@ -291,13 +276,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `DELETE` request to `/api/issues/{projectname}` with an `_id` to delete an issue. If no `_id` is sent, the return value is `{ error: 'missing _id' }`. On success, the return value is `{ result: 'successfully deleted', '_id': _id }`. On failure, the return value is `{ error: 'could not delete', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let initialData = {
|
||||
issue_title: 'Issue to be Deleted',
|
||||
@@ -333,13 +316,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 14 functional tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -355,6 +336,5 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ You can provide your own project, not the example URL.
|
||||
You can send a <b>POST</b> request to `/api/books` with `title` as part of the form data to add a book. The returned response will be an object with the `title` and a unique `_id` as keys. If `title` is not included in the request, the returned response should be the string `missing required field title`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let data1 = await $.post(code + '/api/books', {
|
||||
title: 'Faux Book 1'
|
||||
@@ -49,13 +48,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>GET</b> request to `/api/books` and receive a JSON response representing all the books. The JSON response will be an array of objects with each object (book) containing `title`, `_id`, and `commentcount` properties.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let url = code + '/api/books';
|
||||
let a = $.post(url, { title: 'Faux Book A' });
|
||||
@@ -77,13 +74,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>GET</b> request to `/api/books/{_id}` to retrieve a single object of a book containing the properties `title`, `_id`, and a `comments` array (empty array if no comments present). If no book is found, return the string `no book exists`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let url = code + '/api/books';
|
||||
let noBook = await $.get(url + '/5f665eb46e296f6b9b6a504d');
|
||||
@@ -101,13 +96,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>POST</b> request containing `comment` as the form body data to `/api/books/{_id}` to add a comment to a book. The returned response will be the books object similar to <b>GET</b> `/api/books/{_id}` request in an earlier test. If `comment` is not included in the request, return the string `missing required field comment`. If no book is found, return the string `no book exists`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let url = code + '/api/books';
|
||||
let commentTarget = await $.post(url, { title: 'Notable Book' });
|
||||
@@ -139,13 +132,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>DELETE</b> request to `/api/books/{_id}` to delete a book from the collection. The returned response will be the string `delete successful` if successful. If no book is found, return the string `no book exists`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let url = code + '/api/books';
|
||||
let deleteTarget = await $.post(url, { title: 'Deletable Book' });
|
||||
@@ -163,13 +154,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>DELETE</b> request to `/api/books` to delete all books in the database. The returned response will be the string `complete delete successful` if successful.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const deleteAll = await $.ajax({
|
||||
url: code + '/api/books',
|
||||
@@ -180,13 +169,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 10 functional tests required are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -202,5 +189,4 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -68,7 +68,6 @@ You should provide your own project, not the example URL.
|
||||
You can `POST` `/api/solve` with form data containing `puzzle` which will be a string containing a combination of numbers (1-9) and periods `.` to represent empty spaces. The returned object will contain a `solution` property with the solved puzzle.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output =
|
||||
@@ -81,13 +80,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'solution');
|
||||
assert.equal(parsed.solution, output);
|
||||
};
|
||||
```
|
||||
|
||||
If the object submitted to `/api/solve` is missing `puzzle`, the returned value will be `{ error: 'Required field missing' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output = 'Required field missing';
|
||||
@@ -99,13 +96,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/solve` contains values which are not numbers or periods, the returned value will be `{ error: 'Invalid characters in puzzle' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'AA9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output = 'Invalid characters in puzzle';
|
||||
@@ -117,13 +112,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/solve` is greater or less than 81 characters, the returned value will be `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const inputs = [
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6.',
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6...'
|
||||
@@ -139,13 +132,11 @@ async () => {
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/solve` is invalid or cannot be solved, the returned value will be `{ error: 'Puzzle cannot be solved' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'9.9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output = 'Puzzle cannot be solved';
|
||||
@@ -157,13 +148,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
You can `POST` to `/api/check` an object containing `puzzle`, `coordinate`, and `value` where the `coordinate` is the letter A-I indicating the row, followed by a number 1-9 indicating the column, and `value` is a number from 1-9.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const coordinate = 'A1';
|
||||
@@ -176,13 +165,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'valid');
|
||||
assert.isTrue(parsed.valid);
|
||||
};
|
||||
```
|
||||
|
||||
The return value from the `POST` to `/api/check` will be an object containing a `valid` property, which is `true` if the number may be placed at the provided coordinate and `false` if the number may not. If false, the returned object will also contain a `conflict` property which is an array containing the strings `"row"`, `"column"`, and/or `"region"` depending on which makes the placement invalid.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const coordinate = 'A1';
|
||||
@@ -199,13 +186,11 @@ async () => {
|
||||
assert.property(parsed, 'conflict');
|
||||
assert.include(parsed.conflict, 'row');
|
||||
assert.include(parsed.conflict, 'column');
|
||||
};
|
||||
```
|
||||
|
||||
If `value` submitted to `/api/check` is already placed in `puzzle` on that `coordinate`, the returned value will be an object containing a `valid` property with `true` if `value` is not conflicting.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const coordinate = 'C3';
|
||||
@@ -218,13 +203,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'valid');
|
||||
assert.isTrue(parsed.valid);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/check` contains values which are not numbers or periods, the returned value will be `{ error: 'Invalid characters in puzzle' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'AA9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const coordinate = 'A1';
|
||||
@@ -238,13 +221,11 @@ async () => {
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/check` is greater or less than 81 characters, the returned value will be `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const inputs = [
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6.',
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6...'
|
||||
@@ -262,13 +243,11 @@ async () => {
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If the object submitted to `/api/check` is missing `puzzle`, `coordinate` or `value`, the returned value will be `{ error: 'Required field(s) missing' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const inputs = [
|
||||
{
|
||||
puzzle: '..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..',
|
||||
@@ -294,13 +273,11 @@ async () => {
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If the coordinate submitted to `api/check` does not point to an existing grid cell, the returned value will be `{ error: 'Invalid coordinate'}`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output = 'Invalid coordinate';
|
||||
@@ -316,13 +293,11 @@ async () => {
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If the `value` submitted to `/api/check` is not a number between 1 and 9, the returned value will be `{ error: 'Invalid value' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output = 'Invalid value';
|
||||
@@ -338,13 +313,11 @@ async () => {
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 12 unit tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -363,13 +336,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 14 functional tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -388,5 +359,4 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -73,7 +73,6 @@ You should provide your own project, not the example URL.
|
||||
You can `POST` to `/api/translate` with a body containing `text` with the text to translate and `locale` with either `american-to-british` or `british-to-american`. The returned object should contain the submitted `text` and `translation` with the translated text.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const text = 'Mangoes are my favorite fruit.';
|
||||
const locale = 'american-to-british';
|
||||
@@ -95,13 +94,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `/api/translate` route should handle the way time is written in American and British English. For example, ten thirty is written as "10.30" in British English and "10:30" in American English. The `span` element should wrap the entire time string, i.e. `<span class="highlight">10:30</span>`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const text = 'Lunch is at 12:15 today.';
|
||||
const locale = 'american-to-british';
|
||||
@@ -122,13 +119,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `/api/translate` route should also handle the way titles/honorifics are abbreviated in American and British English. For example, Doctor Wright is abbreviated as "Dr Wright" in British English and "Dr. Wright" in American English. See `/components/american-to-british-titles.js` for the different titles your application should handle.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const text = 'Dr. Grosh will see you now.';
|
||||
const locale = 'american-to-british';
|
||||
@@ -149,13 +144,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Wrap any translated spelling or terms with `<span class="highlight">...</span>` tags so they appear in green.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const text = 'Mangoes are my favorite fruit.';
|
||||
const locale = 'american-to-british';
|
||||
@@ -177,13 +170,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If one or more of the required fields is missing, return `{ error: 'Required field(s) missing' }`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const locale = 'american-to-british';
|
||||
let data = await fetch(code + '/api/translate', {
|
||||
@@ -198,13 +189,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If `text` is empty, return `{ error: 'No text to translate' }`
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const locale = 'american-to-british';
|
||||
let data = await fetch(code + '/api/translate', {
|
||||
@@ -219,13 +208,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If `locale` does not match one of the two specified locales, return `{ error: 'Invalid value for locale field' }`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const text = "Ceci n'est pas une pipe";
|
||||
const locale = 'french-to-american';
|
||||
@@ -241,13 +228,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If `text` requires no translation, return `"Everything looks good to me!"` for the `translation` value.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const locale = 'british-to-american';
|
||||
const output = {
|
||||
@@ -268,13 +253,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 24 unit tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -293,13 +276,11 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 6 functional tests are complete and passing.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const getTests = await $.get(code + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
@@ -318,5 +299,4 @@ async () => {
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -39,7 +39,6 @@ assert(
|
||||
The `DisplayMessages` component should render a `div` containing an `h2` element, a `button` element, a `ul` element, and `li` elements as children.
|
||||
|
||||
```js
|
||||
() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const state = () => {
|
||||
mockedComponent.setState({ messages: ['__TEST__MESSAGE'] });
|
||||
@@ -53,7 +52,6 @@ The `DisplayMessages` component should render a `div` containing an `h2` element
|
||||
updated.find('ul').length === 1 &&
|
||||
updated.find('li').length > 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
`.map` should be used on the `messages` array.
|
||||
@@ -65,7 +63,6 @@ assert(code.match(/this\.state\.messages\.map/g));
|
||||
The `input` element should render the value of `input` in local state.
|
||||
|
||||
```js
|
||||
() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
@@ -76,13 +73,11 @@ The `input` element should render the value of `input` in local state.
|
||||
};
|
||||
const updated = changed();
|
||||
assert(updated.find('input').props().value === testValue);
|
||||
};
|
||||
```
|
||||
|
||||
Calling the method `handleChange` should update the `input` value in state to the current input.
|
||||
|
||||
```js
|
||||
() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
@@ -97,13 +92,11 @@ Calling the method `handleChange` should update the `input` value in state to th
|
||||
initialState.input === '' &&
|
||||
afterInput.state().input === '__TEST__EVENT__MESSAGE__'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Clicking the `Add message` button should call the method `submitMessage` which should add the current `input` to the `messages` array in state.
|
||||
|
||||
```js
|
||||
() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
@@ -138,13 +131,11 @@ Clicking the `Add message` button should call the method `submitMessage` which s
|
||||
submitState_2.messages.length === 2 &&
|
||||
submitState_2.messages[1] === testMessage_2
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
The `submitMessage` method should clear the current input.
|
||||
|
||||
```js
|
||||
() => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages));
|
||||
const causeChange = (c, v) =>
|
||||
c.find('input').simulate('change', { target: { value: v } });
|
||||
@@ -163,7 +154,6 @@ The `submitMessage` method should clear the current input.
|
||||
const afterSubmit = firstSubmit();
|
||||
const submitState = afterSubmit.state();
|
||||
assert(firstState.input === testMessage && submitState.input === '');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -40,8 +40,7 @@ assert(
|
||||
The `Provider` wrapper component should have a prop of `store` passed to it, equal to the Redux store.
|
||||
|
||||
```js
|
||||
() =>
|
||||
assert(
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
return __helpers
|
||||
|
||||
@@ -103,7 +103,6 @@ assert(
|
||||
Typing in the `input` element should update the state of the `Presentational` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const testValue = '__MOCK__INPUT__';
|
||||
const waitForIt = (fn) =>
|
||||
@@ -121,13 +120,11 @@ async () => {
|
||||
initialInput.props().value === '' &&
|
||||
updatedInput.props().value === '__MOCK__INPUT__'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Dispatching the `submitMessage` on the `Presentational` component should update Redux store and clear the input in local state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
@@ -153,13 +150,11 @@ async () => {
|
||||
afterProps.messages.pop() === testValue &&
|
||||
afterClick.find('input').props().value === ''
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
The `Presentational` component should render the `messages` from the Redux store.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(AppWrapper));
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
@@ -186,7 +181,6 @@ async () => {
|
||||
afterClick.find('input').props().value === '' &&
|
||||
afterClick.find('ul').childAt(0).text() === testValue
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -23,8 +23,7 @@ Log the message `'Now I know React and Redux!'` to the console.
|
||||
The message `Now I know React and Redux!` should be logged to the console.
|
||||
|
||||
```js
|
||||
() =>
|
||||
assert(
|
||||
assert(
|
||||
/console\s*\.\s*log\s*\(\s*('|"|`)Now I know React and Redux!\1\s*\)/.test(
|
||||
code)
|
||||
);
|
||||
|
||||
@@ -43,8 +43,7 @@ assert(
|
||||
The `Items` component should have a prop of `{ quantity: 10 }` passed from the `ShoppingCart` component.
|
||||
|
||||
```js
|
||||
() =>
|
||||
assert(
|
||||
assert(
|
||||
(function () {
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart));
|
||||
return (
|
||||
|
||||
@@ -46,7 +46,6 @@ assert(
|
||||
The rendered `h1` heading element should only contain text rendered from the component's state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -57,7 +56,6 @@ async () => {
|
||||
const firstValue = await first();
|
||||
const getValue = firstValue.replace(/\s/g, '');
|
||||
assert(getValue === '<div><h1>TestName</h1></div>');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -46,7 +46,6 @@ assert(/<h1>\n*\s*\{\s*name\s*\}\s*\n*<\/h1>/.test(code));
|
||||
The rendered `h1` heading element should contain text rendered from the component's state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -56,7 +55,6 @@ async () => {
|
||||
};
|
||||
const firstValue = await first();
|
||||
assert(firstValue === '<div><h1>TestName</h1></div>');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -44,7 +44,6 @@ assert(Enzyme.mount(React.createElement(MyComponent)).find('h1').length === 1);
|
||||
The rendered `h1` heading element should contain text rendered from the component's state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -54,13 +53,11 @@ async () => {
|
||||
};
|
||||
const firstValue = await first();
|
||||
assert(/<h1>TestName<\/h1>/.test(firstValue));
|
||||
};
|
||||
```
|
||||
|
||||
Calling the `handleClick` method on `MyComponent` should set the name property in state to equal `React Rocks!`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -75,7 +72,6 @@ async () => {
|
||||
const firstValue = await first();
|
||||
const secondValue = await second();
|
||||
assert(firstValue === 'Before' && secondValue === 'React Rocks!');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -51,7 +51,6 @@ assert(
|
||||
Clicking the `button` element should run the `handleClick` method and set the state `text` to `You clicked!`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -66,7 +65,6 @@ async () => {
|
||||
const firstValue = await first();
|
||||
const secondValue = await second();
|
||||
assert(firstValue === 'Hello' && secondValue === 'You clicked!');
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -51,7 +51,6 @@ assert.strictEqual(
|
||||
Typing in the input element should update the state and the value of the input, and the `p` element should render this state as you type.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(ControlledInput));
|
||||
@@ -77,7 +76,6 @@ async () => {
|
||||
after.text === 'TestInput' &&
|
||||
after.inputVal === 'TestInput'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -37,7 +37,6 @@ assert(
|
||||
The `Navbar` component should receive the `MyApp` state property `name` as props.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
@@ -47,13 +46,11 @@ async () => {
|
||||
};
|
||||
const navProps = await setState();
|
||||
assert(navProps.name === 'TestName');
|
||||
};
|
||||
```
|
||||
|
||||
The `h1` element in `Navbar` should render the `name` prop.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
@@ -64,7 +61,6 @@ async () => {
|
||||
};
|
||||
const navH1After = await setState();
|
||||
assert(new RegExp('TestName').test(navH1After) && navH1After !== navH1Before);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -54,7 +54,6 @@ assert(
|
||||
The `GetInput` component should receive the `MyApp` state property `inputValue` as props and contain an `input` element which modifies `MyApp` state.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
@@ -71,13 +70,11 @@ async () => {
|
||||
const updated_1 = await state_1();
|
||||
const updated_2 = await state_2();
|
||||
assert(updated_1.inputValue === '' && updated_2.inputValue === 'TestInput');
|
||||
};
|
||||
```
|
||||
|
||||
The `RenderInput` component should receive the `MyApp` state property `inputValue` as props.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyApp));
|
||||
@@ -87,7 +84,6 @@ async () => {
|
||||
};
|
||||
const updated_1 = await state_1();
|
||||
assert(updated_1.find('p').text().includes('TestName'));
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -66,7 +66,6 @@ assert(
|
||||
Once the component has mounted, pressing `enter` should update its state and the rendered `h1` tag.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -86,7 +85,6 @@ async () => {
|
||||
assert(
|
||||
beforeState !== afterKeyPress.state && beforeText !== afterKeyPress.text
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -32,7 +32,6 @@ assert(
|
||||
When `display` is set to `true`, a `div`, `button`, and `h1` should render.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -47,13 +46,11 @@ async () => {
|
||||
mockedComponent.find('button').length === 1 &&
|
||||
mockedComponent.find('h1').length === 1
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
When `display` is set to `false`, only a `div` and `button` should render.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -68,7 +65,6 @@ async () => {
|
||||
mockedComponent.find('button').length === 1 &&
|
||||
mockedComponent.find('h1').length === 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
The render method should use an `if/else` statement to check the condition of `this.state.display`.
|
||||
|
||||
@@ -36,7 +36,6 @@ assert(
|
||||
When `display` is set to `true`, a `div`, `button`, and `h1` should render.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -51,13 +50,11 @@ async () => {
|
||||
updated.find('button').length === 1 &&
|
||||
updated.find('h1').length === 1
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
When `display` is set to `false`, only a `div` and `button` should render.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 250));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(MyComponent));
|
||||
@@ -72,7 +69,6 @@ async () => {
|
||||
updated.find('button').length === 1 &&
|
||||
updated.find('h1').length === 0
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
The render method should use the `&&` logical operator to check the condition of `this.state.display`.
|
||||
|
||||
@@ -70,7 +70,6 @@ assert(
|
||||
The `input` tag should be styled with a border of `3px solid red` if the input value in state is longer than 15 characters.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const waitForIt = (fn) =>
|
||||
new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100));
|
||||
const mockedComponent = Enzyme.mount(React.createElement(GateKeeper));
|
||||
@@ -94,7 +93,6 @@ async () => {
|
||||
style_1 === '1px solid black' &&
|
||||
style_2 === '3px solid red'
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -21,7 +21,6 @@ The `renderToString()` method is provided on `ReactDOMServer`, which is availabl
|
||||
The `App` component should render to a string using `ReactDOMServer.renderToString`.
|
||||
|
||||
```js
|
||||
() =>
|
||||
assert(
|
||||
code.replace(/ /g, '')
|
||||
.includes('ReactDOMServer.renderToString(<App/>)') &&
|
||||
|
||||
@@ -29,7 +29,7 @@ assert.lengthOf(messages, 0, messages[0]);
|
||||
scoreInputs[0].value = "10";
|
||||
scoreInputs[0].checked = true;
|
||||
keepScoreBtn.click();
|
||||
new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
|
||||
assert.lengthOf(messages, 1);
|
||||
alert = oldAlert;
|
||||
window.alert = oldWindow;
|
||||
|
||||
@@ -103,7 +103,7 @@ document.querySelector('#none').click();
|
||||
keepScoreBtn.click();
|
||||
assert.lengthOf(messages, 0);
|
||||
assert.isFalse(called);
|
||||
new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
|
||||
assert.lengthOf(messages, 1);
|
||||
assert.isTrue(called);
|
||||
resetGame = oldReset;
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('img'), "'img' element not found");
|
||||
await retryingTest(() => document.querySelector('img'), "'img' element not found");
|
||||
```
|
||||
|
||||
Your `img` element should have the class `"user-img"`.
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('p'), "'p' element not found");
|
||||
await retryingTest(() => document.querySelector('p'), "'p' element not found");
|
||||
```
|
||||
|
||||
Your `p` element should have the class `"bio"`
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('a'), "'a' element not found");
|
||||
await retryingTest(() => document.querySelector('a'), "'a' element not found");
|
||||
```
|
||||
|
||||
Your anchor element should have the class `"author-link"`.
|
||||
|
||||
@@ -34,7 +34,7 @@ const retryingTest = (test, message, tries = 20) => {
|
||||
}, 1);
|
||||
});
|
||||
};
|
||||
() => retryingTest(() => document.querySelector('p')?.previousElementSibling?.tagName === 'DIV', "'div' element not found");
|
||||
await retryingTest(() => document.querySelector('p')?.previousElementSibling?.tagName === 'DIV', "'div' element not found");
|
||||
```
|
||||
|
||||
Your `div` element should have the `class` set to `"purple-divider"`.
|
||||
|
||||
@@ -37,27 +37,23 @@ assert.match(code, /(const|let|var)\s+\[\s*query\s*,\s*setQuery\s*\]\s*/);
|
||||
You should use the `useState` Hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.FruitsSearch);
|
||||
assert.isAtLeast(abuseState.calls.length, 1);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` Hook should have an initial value of an empty string (`''`).
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.FruitsSearch);
|
||||
assert.equal(abuseState.calls[0]?.[0], '');
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -22,20 +22,17 @@ assert.match(code, /(const|let|var)\s+\[\s*(results)\s*,\s*(setResults)\s*\]\s*/
|
||||
You should use the `useState` Hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.FruitsSearch);
|
||||
assert.isAtLeast(abuseState.calls.length, 2);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` Hook should have an initial value of an empty array (`[]`).
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -43,7 +40,6 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.FruitsSearch);
|
||||
|
||||
assert.deepEqual(abuseState.calls[1]?.[0], []);
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -16,7 +16,6 @@ If `results` it's empty, render a `p` element with the message `No results found
|
||||
Your `#results` container should contain a list of matching fruits based on the query. Each result should be a `p` element with the class of `result-item` and should show the name of the fruit.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
{
|
||||
const mockResults = ["Item 1", "Item 2"];
|
||||
React.useState = (arg) => {
|
||||
@@ -36,13 +35,11 @@ async () => {
|
||||
assert.isTrue(items[0].classList.contains("result-item"));
|
||||
assert.isTrue(items[1].classList.contains("result-item"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Your `#results` element should contain a `p` element with the text `No results found` when the `results` array is empty.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
{
|
||||
React.useState = (arg) => {
|
||||
if (Array.isArray(arg)) return [[], () => {}];
|
||||
@@ -56,7 +53,6 @@ async () => {
|
||||
assert.exists(noResultsEl);
|
||||
assert.equal(noResultsEl.textContent, "No results found");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -14,20 +14,16 @@ Inside the `return` statement, remove the empty string, then create a `div` elem
|
||||
Your `Card` component should not render an empty string.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card);
|
||||
assert.notEqual(testElem.innerHTML, '');
|
||||
}
|
||||
```
|
||||
|
||||
You should create a `div` element with the `className` of `card` at the top level of your `Card` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card);
|
||||
const div = testElem.firstElementChild;
|
||||
assert.include([...div?.classList], 'card');
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -16,56 +16,46 @@ Also, inside the `div`, create a paragraph with the `className` of `card-title`
|
||||
You should create an `h2` element inside your `div` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card);
|
||||
const h2Elem = testElem.querySelector('div > h2');
|
||||
assert.exists(h2Elem);
|
||||
}
|
||||
```
|
||||
|
||||
Your `h2` element should have `{name}` as its text content.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card, { name: 'nameVal' });
|
||||
const h2Elem = testElem.querySelector('div > h2');
|
||||
// trimming because we don't need to be picky about whitespace
|
||||
assert.equal(h2Elem.textContent.trim(), 'nameVal');
|
||||
}
|
||||
```
|
||||
|
||||
You should create a `p` tag with the `className` of `card-title` inside your `div` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card);
|
||||
const pElem = testElem.querySelector('div > p.card-title');
|
||||
assert.exists(pElem);
|
||||
}
|
||||
```
|
||||
|
||||
Your `p` element with `className` of `card-title` should have `{title}` as its text content.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card, { title: 'titleVal' });
|
||||
const pElem = testElem.querySelector('div > p.card-title');
|
||||
// trimming because we don't need to be picky about whitespace
|
||||
assert.equal(pElem.textContent.trim(), 'titleVal');
|
||||
}
|
||||
```
|
||||
|
||||
You should create another `p` element with `{bio}` as its text.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.Card, {title: 'titleVal', bio: 'bioVal' });
|
||||
// trimming because we don't need to be picky about whitespace
|
||||
const texts = [...testElem.querySelectorAll('div > p')].map((p) => p.textContent.trim());
|
||||
const bioText = texts.find((text) => text.includes('bioVal'));
|
||||
assert.exists(bioText);
|
||||
assert.equal(bioText, 'bioVal');
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -14,29 +14,23 @@ Inside your return statement, replace the empty string with a `div` element with
|
||||
Your `App` component should not render an empty string.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
assert.notEqual(testElem.innerHTML, '');
|
||||
}
|
||||
```
|
||||
|
||||
Your `App` component should return a single `div` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
assert.equal(testElem.firstElementChild?.tagName, 'DIV');
|
||||
assert.lengthOf(testElem.children, 1);
|
||||
}
|
||||
```
|
||||
|
||||
Your `div` should have a `className` property of `flex-container`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
assert.include([...testElem.firstElementChild.classList], 'flex-container');
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -14,41 +14,33 @@ Now, put the `Card` component into your `App` component. Give it the `name` prop
|
||||
You should use the `Card` component in your `App` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
const card = testElem.querySelector('.card');
|
||||
assert.exists(card);
|
||||
}
|
||||
```
|
||||
|
||||
You should pass the `name` prop with value `"Mark"` to your `Card` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
const card = testElem.querySelector('.card');
|
||||
assert.equal(card.querySelector('h2').textContent, 'Mark');
|
||||
}
|
||||
```
|
||||
|
||||
You should pass the `title` prop with value `"Frontend developer"` to your `Card` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
const card = testElem.querySelector('.card');
|
||||
assert.equal(card.querySelector('.card-title').textContent, 'Frontend developer');
|
||||
}
|
||||
```
|
||||
|
||||
You should pass the `bio` prop with value `"I like to work with different frontend technologies and play video games."` to your `Card` component.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.App);
|
||||
const card = testElem.querySelector('.card');
|
||||
assert.equal(card.querySelector('p:last-child').textContent, 'I like to work with different frontend technologies and play video games.');
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -16,35 +16,29 @@ Inside the `div`, nest an `h1` element with the text `Shopping List`, and below
|
||||
Your `ShoppingList` component should return a `div` element with a `className` of `container`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
assert.equal(testElem.firstElementChild?.tagName, 'DIV');
|
||||
assert.isTrue(
|
||||
[...testElem.firstElementChild.classList].includes('container')
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Your `div` should contain an `h1` element with the text `Shopping List`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const h1 = testElem.querySelector('h1');
|
||||
assert.exists(h1);
|
||||
assert.equal(h1.textContent.toLowerCase().trim(), 'shopping list');
|
||||
}
|
||||
```
|
||||
|
||||
Your `div` should also contain an empty `form` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const form = testElem.querySelector('form');
|
||||
assert.exists(form);
|
||||
assert.equal(form.tagName, 'FORM');
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -18,66 +18,54 @@ Finally, add a `p` element below the `input`, and give it the text `Type to filt
|
||||
Your `form` element should contain a `label` element with the text `Search for an item:`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const label = testElem.querySelector('form label');
|
||||
assert.exists(label);
|
||||
assert.equal(label.textContent.toLowerCase().trim(), 'search for an item:');
|
||||
}
|
||||
```
|
||||
|
||||
Your `label` should have an `htmlFor` attribute set to `search`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const label = testElem.querySelector('form label');
|
||||
assert.equal(label.getAttribute('for'), 'search');
|
||||
}
|
||||
```
|
||||
|
||||
Your `form` element should contain an `input` element with the `type` and `id` attributes set to `search`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const input = testElem.querySelector('form input');
|
||||
assert.exists(input);
|
||||
assert.equal(input.getAttribute('type'), 'search');
|
||||
assert.equal(input.getAttribute('id'), 'search');
|
||||
}
|
||||
```
|
||||
|
||||
Your `input` should have a placeholder of `Search...` and an `aria-describedby` attribute set to `search-description`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const input = testElem.querySelector('form input');
|
||||
assert.equal(input.getAttribute('placeholder').toLowerCase().trim(), 'search...');
|
||||
assert.equal(input.getAttribute('aria-describedby'), 'search-description');
|
||||
}
|
||||
```
|
||||
|
||||
Your `form` element should contain a `p` element with the text `Type to filter the list below:`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const p = testElem.querySelector('form p');
|
||||
assert.exists(p);
|
||||
assert.equal(p.textContent.toLowerCase().trim(), 'type to filter the list below:');
|
||||
}
|
||||
```
|
||||
|
||||
Your `p` element should have an `id` of `search-description`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const p = testElem.querySelector('form p');
|
||||
assert.equal(p.getAttribute('id'), 'search-description');
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -16,10 +16,8 @@ First, add a `ul` element below the `p` element in the `form`. Inside the unorde
|
||||
Your `form` element should contain a `ul` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
assert.exists(testElem.querySelector('form ul'));
|
||||
}
|
||||
```
|
||||
|
||||
You should use the `map()` method on the `filteredItems` array within the `ul` element.
|
||||
|
||||
@@ -16,18 +16,15 @@ Finally, wrap the `input` and the `item` text in a `label` element for better ac
|
||||
You should add another `input` element with the `type` attribute set to `checkbox`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const checkbox = testElem.querySelector('input[type="checkbox"]');
|
||||
|
||||
assert.exists(checkbox);
|
||||
}
|
||||
```
|
||||
|
||||
Your new `input` should have an `onChange` attribute set to an anonymous function that calls `toggleItem()` with `item` as an argument.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const checkbox = testElem.querySelector('input[type="checkbox"]');
|
||||
const key = checkbox && Object.keys(checkbox).find(key => key.startsWith("__reactProps"));
|
||||
@@ -35,13 +32,11 @@ async () => {
|
||||
const onChangeString = reactCheckbox && reactCheckbox?.onChange?.toString();
|
||||
|
||||
assert.match(onChangeString, /function onChange\s*\(\s*\)\s*{\s*(return)?\s+toggleItem\s*\(\s*item\s*\);?\s*\}/);
|
||||
}
|
||||
```
|
||||
|
||||
Your new `input` and the `item` text should be wrapped in a `label` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const label = testElem.querySelector('li label');
|
||||
const key = label && Object.keys(label)?.find(key => key.startsWith("__reactProps"));
|
||||
@@ -51,7 +46,6 @@ async () => {
|
||||
assert.lengthOf(reactLabelChildren, 2);
|
||||
assert.isTrue(reactLabelChildren.some((item => item.type === 'input')));
|
||||
assert.isTrue(reactLabelChildren.some((item => typeof item === 'string')));
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -39,14 +39,12 @@ assert.match(shoppingListString, /var\s*isChecked\s*=\s*selectedItems\.includes\
|
||||
You should add a `style` prop to the `li` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const li = testElem.querySelector('li');
|
||||
const key = li && Object.keys(li)?.find(key => key.startsWith("__reactProps"));
|
||||
const reactLi = li && li[key];
|
||||
|
||||
assert.exists(reactLi.style);
|
||||
}
|
||||
```
|
||||
|
||||
Your `style` prop should include a ternary operator that sets the `textDecoration` property to `line-through` if `isChecked` is true, or `none` if it is false.
|
||||
|
||||
@@ -14,14 +14,12 @@ Then, in the checkbox `input` element, add a `checked` attribute and set it to `
|
||||
You should add a `checked` attribute to the checkbox `input` element.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const testElem = await __helpers.prepTestComponent(window.index.ShoppingList);
|
||||
const checkbox = testElem.querySelector('input[type="checkbox"]');
|
||||
const key = checkbox && Object.keys(checkbox)?.find(key => key.startsWith("__reactProps"));
|
||||
const reactCheckbox = checkbox && checkbox[key];
|
||||
|
||||
assert.exists(reactCheckbox.checked);
|
||||
}
|
||||
```
|
||||
|
||||
You should set the `checked` attribute to `isChecked`.
|
||||
|
||||
@@ -22,7 +22,6 @@ assert.match(code, /(const|let|var)\s+\[\s*heroName\s*,\s*setHeroName\s*\]/);
|
||||
Your `heroName` and `setHeroName` should use the `useState` hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -30,13 +29,11 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.isAtLeast(abuseState.calls.length, 1);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` hook for `heroName` should have an initial value of empty string.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -44,7 +41,6 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.equal(abuseState.calls[0]?.[0], "");
|
||||
}
|
||||
```
|
||||
|
||||
You should use the array destructuring syntax to set a `realName` state variable and a `setRealName` setter.
|
||||
@@ -56,7 +52,6 @@ assert.match(code, /(const|let|var)\s+\[\s*realName\s*,\s*setRealName\s*\]/);
|
||||
Your `realName` and `setRealName` should use the `useState` hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -64,13 +59,11 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.isAtLeast(abuseState.calls.length, 2);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` hook for `realName` should have an initial value of empty string.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -78,7 +71,6 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.equal(abuseState.calls[1]?.[0], "");
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -20,7 +20,6 @@ assert.match(code, /(const|let|var)\s+\[\s*powerSource\s*,\s*setPowerSource\s*\]
|
||||
Your `powerSource` and `setPowerSource` should use the `useState` hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -28,13 +27,11 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.isAtLeast(abuseState.calls.length, 3);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` hook for `powerSource` should have an initial value of empty string.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -42,7 +39,6 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.equal(abuseState.calls[2]?.[0], "");
|
||||
}
|
||||
```
|
||||
|
||||
You should use array destructuring to set a `powers` state variable and a `setPowers` setter.
|
||||
@@ -54,7 +50,6 @@ assert.match(code, /(const|let|var)\s+\[\s*powers\s*,\s*setPowers\s*\]/);
|
||||
Your `powers` and `setPowers` should use the `useState` hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -62,13 +57,11 @@ async () => {
|
||||
const _b = await __helpers.prepTestComponent(exports.SuperheroForm);
|
||||
|
||||
assert.isAtLeast(abuseState.calls.length, 4);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` hook for `powers` should have an initial value of empty array.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
@@ -78,7 +71,6 @@ async () => {
|
||||
console.log("State calls:", abuseState.calls)
|
||||
|
||||
assert.deepEqual(abuseState.calls[3]?.[0], []);
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -32,7 +32,6 @@ assert.match(code, /(const|let|var)\s+\[\s*(isVisible)\s*,\s*(setIsVisible)\s*\]
|
||||
You should use the `useState` hook.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
@@ -41,13 +40,11 @@ async () => {
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.ToggleApp);
|
||||
assert.isAtLeast(abuseState.calls.length, 1);
|
||||
}
|
||||
```
|
||||
|
||||
Your `useState` hook should have an initial value of `false`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
@@ -56,7 +53,6 @@ async () => {
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.ToggleApp);
|
||||
assert.equal(abuseState.calls[0]?.[0], false);
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -30,7 +30,6 @@ For this step, you will need to conditionally render the paragraph element. When
|
||||
You should conditionally render the paragraph so it only shows when `isVisible` is `true`. Refer back to the example if you need help.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
{
|
||||
React.useState = (arg) => ([false, () => {}]);
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
@@ -49,7 +48,6 @@ async () => {
|
||||
const pEl = div.querySelector("p#message");
|
||||
assert.exists(pEl);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -16,14 +16,12 @@ Change the current `useState` value from `false` to `true` and you should see th
|
||||
You should change the initial value for `useState` to `true`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.ToggleApp);
|
||||
assert.equal(abuseState.calls[0]?.[0], true);
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -18,14 +18,12 @@ In the next few steps, you will build out the button functionality that will han
|
||||
You should set the initial value for the `useState` hook back to `false`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText;
|
||||
const exports = {};
|
||||
const _a = eval(script);
|
||||
const _b = await __helpers.prepTestComponent(exports.ToggleApp);
|
||||
assert.equal(abuseState.calls[0]?.[0], false);
|
||||
}
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -30,7 +30,6 @@ Now, when you click on the button, you will see the visibility of the paragraph
|
||||
When the `toggle-button` is clicked, the message `"I love freeCodeCamp!"` should appear on the screen.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText.replace("_React.useState", "abuseState");
|
||||
|
||||
@@ -66,7 +65,6 @@ async () => {
|
||||
|
||||
abuseState.restore();
|
||||
assert.isTrue(stateChanged);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -33,7 +33,6 @@ assert.equal(btn.textContent, "Show Message");
|
||||
When `isVisible` is `true`, the button text should say `Hide Message`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
const abuseState = __helpers.spyOn(React, "useState");
|
||||
const script = [...document.querySelectorAll("script")].find((s) => s.dataset.src === "index.jsx").innerText.replace("_React.useState", "abuseState");
|
||||
|
||||
@@ -68,7 +67,6 @@ async () => {
|
||||
|
||||
abuseState.restore();
|
||||
assert.isTrue(stateChanged);
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
Reference in New Issue
Block a user