chore(i18n,curriculum): processed translations - new ukrainian (#44447)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
---
|
||||
id: 589fc832f9fc0f352b528e78
|
||||
title: Повідомлення про нових користувачів
|
||||
challengeType: 2
|
||||
forumTopicId: 301546
|
||||
dashedName: announce-new-users
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Багато чатів мають функцію повідомлення всіх підключених користувачів чату про приєднання чи від'єднання користувача. Оскільки приєднання чи від'єднання це вже виконання дії, то для підтримки такої функції вам потрібно відредагувати цю подію. Найбільш логічний спосіб зробити це — надіслати певні дані з подією: ім’я користувача, який приєднався/від'єднався, поточну кількість користувачів, а також те, чи це ім’я приєднане чи від'єднане.
|
||||
|
||||
Змініть назву події на `'user'` і надішліть інформацію, що містить поля 'name', 'currentUsers' і 'connected' (`true` у разі приєднання, або - `false` для відключення користувача). Обов’язково переконайтесь, що ви змінили події 'user count' та встановили `false` при відключенні для поля 'connected' замість `true`, як і при підключенні.
|
||||
|
||||
```js
|
||||
io.emit('user', {
|
||||
name: socket.request.user.name,
|
||||
currentUsers,
|
||||
connected: true
|
||||
});
|
||||
```
|
||||
|
||||
Тепер ваш клієнт матиме всю необхідну інформацію, щоб правильно показати кількість поточних користувачів і повідомити, коли користувач підключається або відключається! Щоб обробити цю подію зі сторони клієнта, ми повинні отримати `'user'`, а потім оновити кількість поточних користувачів за допомогою jQuery, щоб замінити текст `#num-users` на `'{NUMBER} users online'`, а також додати `<li>` до невпорядкованого маркованого списку з id `messages` з `'{NAME} has {joined/left} the chat.'`.
|
||||
|
||||
Ця дія може виглядати таким чином:
|
||||
|
||||
```js
|
||||
socket.on('user', data => {
|
||||
$('#num-users').text(data.currentUsers + ' users online');
|
||||
let message =
|
||||
data.name +
|
||||
(data.connected ? ' has joined the chat.' : ' has left the chat.');
|
||||
$('#messages').append($('<li>').html('<b>' + message + '</b>'));
|
||||
});
|
||||
```
|
||||
|
||||
Відправте сторінку, якщо все було виконано правильно. Якщо виникла помилка, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/bf95a0f74b756cf0771cd62c087b8286).
|
||||
|
||||
# --hints--
|
||||
|
||||
Подія `'user'` повинна містити name, currentUsers та connected.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user\1.*name.*currentUsers.*connected/gis,
|
||||
'You should have an event emitted named user sending name, currentUsers, and connected'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Клієнт повинен правильно обробляти та показувати нові дані із події `'user'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*num-users/gi,
|
||||
'You should change the text of "#num-users" within on your client within the "user" event listener to show the current users connected'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user\1[^]*messages.*li/gi,
|
||||
'You should append a list item to "#messages" on your client within the "user" event listener to announce a user came or went'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,96 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e68
|
||||
title: Стратегії автентифікації
|
||||
challengeType: 2
|
||||
forumTopicId: 301547
|
||||
dashedName: authentication-strategies
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Стратегія - це процес авторизації користувача. Ви можете вибрати стратегію, яка дозволяє користувачам авторизуватись на основі збережених даних (якщо ви їх вказували) або від різних провайдерів таких, як Google або GitHub. До цього проєкту ми встановимо локальну стратегію. Для того, щоб переглянути перелік сотень стратегій, перейдіть на сайт Passport [сюди](http://passportjs.org/).
|
||||
|
||||
Вкажіть `passport-local@~1.0.0` як залежний елемент і додайте його на ваш сервер, як показано далі: `const LocalStrategy = require('passport-local');`
|
||||
|
||||
Далі ви повинні вказати паспорту (passport) **використати (use)** макет об'єкту LocalStrategy із певними вказаними параметрами. Переконайтесь у тому, що це (як і все з цього моменту) інкапсульовано в з'єднанні з базою даних (database), оскільки воно залежить від цього!
|
||||
|
||||
```js
|
||||
passport.use(new LocalStrategy(
|
||||
function(username, password, done) {
|
||||
myDataBase.findOne({ username: username }, function (err, user) {
|
||||
console.log('User '+ username +' attempted to log in.');
|
||||
if (err) { return done(err); }
|
||||
if (!user) { return done(null, false); }
|
||||
if (password !== user.password) { return done(null, false); }
|
||||
return done(null, user);
|
||||
});
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
Так виглядає процес, коли ми хочемо авторизувати когось локально. Спершу система шукає користувача у нашій базі даних за допомогою введеного ім'я користувача, потім перевіряє його за паролем, і тоді, якщо не виникло жодних помилок під час перевірки, як, наприклад, невірний пароль, об'єкт `user` повертається та вважається авторизованим.
|
||||
|
||||
Багато стратегій створюються на основі різних налаштувань, але, як правило, це легко зробити звернувшись до файлу README у репозиторії цієї стратегії. Як хороший приклад можна зазначити стратегію GitHub, де ми можемо не переживати через ім'я користувача або пароль, оскільки користувач автоматично переадресовується на сторінку аутентифікації GitHub для авторизації. Допоки користувач буде залишатись авторизованим і буде згідний з умовами, GitHub надаватиме його профіль для користування.
|
||||
|
||||
Далі ми дізнаємося, як за допомогою стратегії авторизації перевірити користувача на основі заповнених даних!
|
||||
|
||||
Відправте вашу сторінку, якщо все зрозуміло. Якщо сталась якась помилка, ви маєте змогу перевірити статус проєкту до цього етапу [ тут ](https://gist.github.com/camperbot/53b495c02b92adeee0aa1bd3f3be8a4b).
|
||||
|
||||
# --hints--
|
||||
|
||||
Passport-local повинен бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-local',
|
||||
'Your project should list "passport-local " as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Запит Passport-local повинен бути вірно надісланий та налаштований.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-local("|')/gi,
|
||||
'You should have required passport-local'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new LocalStrategy/gi,
|
||||
'You should have told passport to use a new strategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/findOne/gi,
|
||||
'Your new local strategy should use the findOne query to find a username based on the inputs'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,154 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e77
|
||||
title: Автентифікація за допомогою Socket.IO
|
||||
challengeType: 2
|
||||
forumTopicId: 301548
|
||||
dashedName: authentication-with-socket-io
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Наразі ви не можете визначити, хто підключений до вашого веб-сокету. Хоча й `req.user` містить об'єкт "користувач", проте тільки тоді, коли ваш користувач взаємодіє з веб-сервером, тоді ж як до веб-сокетів у вас немає `req` (запиту), а, як наслідок, немає і даних користувача. Одним зі способів розв'язання проблеми відсутності інформації про те, хто підключений до вашого веб-сокету, є застосування синтаксичного аналізу та декодування файлів cookie, які містять сесію паспорту, далі - отриманні дані десеріалізуються задля отримання об'єкту "користувач". На щастя, NPM має спеціальний пакет для цього, який перетворює колись складну задачу на щось просте!
|
||||
|
||||
Додайте `passport.socketio@~3.7.0`, `connect-mongo@~3.2.0` та `cookie-parser@~1.4.5` як залежності та встановіть(require) їх як `passportSocketIo`, `MongoStore` та `cookieParser` відповідно. Крім того, нам потрібно ініціалізувати нове сховище пам'яті з `express-session`, яке ми вже потребували (required). Воно повинно мати такий вигляд:
|
||||
|
||||
```js
|
||||
const MongoStore = require('connect-mongo')(session);
|
||||
const URI = process.env.MONGO_URI;
|
||||
const store = new MongoStore({ url: URI });
|
||||
```
|
||||
|
||||
Тепер треба лише вказати Socket.IO використовувати його та встановити налаштування. Переконайтеся, що його додано перед наявним кодом сокета, а не в цьому слухачі підключення. На вашому сервері це повинно мати такий вигляд:
|
||||
|
||||
```js
|
||||
io.use(
|
||||
passportSocketIo.authorize({
|
||||
cookieParser: cookieParser,
|
||||
key: 'express.sid',
|
||||
secret: process.env.SESSION_SECRET,
|
||||
store: store,
|
||||
success: onAuthorizeSuccess,
|
||||
fail: onAuthorizeFail
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
Зауважте, що налаштування автентифікації Passport для Socket.IO є дуже подібним до налаштування проміжного програмного забезпечення `session` для API. Це пояснюється тим, що вони призначені для одного методу автентифікації: отримати id сесії від файлів cookie та перевірити його.
|
||||
|
||||
Раніше, при налаштуванні проміжного програмного забезпечення `session`, ми не встановлювали безпосередньо назву файлів cookie для сесії (`key`). Це пояснюється тим, що пакет `session` використовував значення за замовчуванням. Тепер, коли додано інший пакет, який потребує доступу до того самого значення файлів cookies, треба чітко встановити значення `key` в обох об'єктах конфігурації.
|
||||
|
||||
Обов'язково виконайте додавання `key` з назвою файлів cookie до проміжного програмного забезпечення `session`, яке збігається з ключем Socket.IO. Також додайте посилання `store` на налаштування, поруч із якими ми пропишемо `saveUninitialized: true`. Необхідно вказати Socket.IO до якої сесії прив'язуватися.
|
||||
|
||||
<hr />
|
||||
|
||||
Тепер слід визначити функції зворотного зв'язку `success` та `fail`:
|
||||
|
||||
```js
|
||||
function onAuthorizeSuccess(data, accept) {
|
||||
console.log('successful connection to socket.io');
|
||||
|
||||
accept(null, true);
|
||||
}
|
||||
|
||||
function onAuthorizeFail(data, message, error, accept) {
|
||||
if (error) throw new Error(message);
|
||||
console.log('failed connection to socket.io:', message);
|
||||
accept(null, false);
|
||||
}
|
||||
```
|
||||
|
||||
Об'єкт "користувач" тепер має доступ у вашому сокет-об'єкті як `socket.request.user`. Тепер, наприклад, ви можете додати таке:
|
||||
|
||||
```js
|
||||
console.log('user ' + socket.request.user.name + ' connected');
|
||||
```
|
||||
|
||||
Це дозволить увійти у підключену консоль серверу!
|
||||
|
||||
Відправте сторінку, якщо все було виконано правильно. Якщо сталась якась помилка, то можна перевірити проєкт до цього етапу [ тут ](https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254).
|
||||
|
||||
# --hints--
|
||||
|
||||
`passport.socketio` має бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport.socketio',
|
||||
'Your project should list "passport.socketio" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
`cookie-parser` має бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'cookie-parser',
|
||||
'Your project should list "cookie-parser" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
passportSocketIo має правильно виконуватися (required).
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require\((['"])passport\.socketio\1\)/gi,
|
||||
'You should correctly require and instantiate "passport.socketio"'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
passportSocketIo слід належним чином налаштувати.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io\.use\(\s*\w+\.authorize\(/,
|
||||
'You should register "passport.socketio" as socket.io middleware and provide it correct options'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: 589690e6f9fc0f352b528e6e
|
||||
title: Очистити ваш проект з модулями
|
||||
challengeType: 2
|
||||
forumTopicId: 301549
|
||||
dashedName: clean-up-your-project-with-modules
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Зараз все, що ви маєте є у файлі `server.js`. Це може привести до складного управління кодом, який не дуже розширюється. Створіть 2 нових файли: `routes.js` і `auth.js`
|
||||
|
||||
Обидва повинні починатися з такого коду:
|
||||
|
||||
```js
|
||||
module.exports = function (app, myDataBase) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Тепер, зверху вашого серверного файлу, подайте ці файли так: `const routes = require('./routes.js');`. Одразу після того, як ви встановите вдалий зв'язок з базою даних, пропишіть кожен з них таким чином: `routes(app, myDataBase)`
|
||||
|
||||
Врешті, візьміть усі ваші маршрути на сервері та вставте їх в нові файли, і видаліть їх з вашого серверного файлу. Також візьміть функцію `ensureAuthenticated`, оскільки її створили спеціально для маршрутизації. Тепер ви маєте правильно додати залежності, в яких використовуються функції, такі як `const passport = require('passport');`, з самого верху, над експортним рядком у вашому файлі `routes.js`.
|
||||
|
||||
Продовжуйте додавати їх, поки помилки не припинять існувати, а ваш серверний файл більше не матиме маршрутизування (**за винятком шляху в блок catch**)!
|
||||
|
||||
Тепер зробіть те саме в вашому файлі auth.js з усім, що пов'язане з автентифікацією, як ось серіалізація і налаштування локальної стратегії, і видаліть їх з вашого серверного файлу. Обов'язково додайте залежності і наберіть `auth(app, myDataBase)` на сервері в цьому ж місці.
|
||||
|
||||
Підтвердьте вашу сторінку, якщо все зрозуміло. Якщо ви натрапите на помилки, можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92).
|
||||
|
||||
# --hints--
|
||||
|
||||
Модулі мають бути присутні.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require\s*\(('|")\.\/routes(\.js)?\1\)/gi,
|
||||
'You should have required your new files'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/client\s*\.db[^]*routes/gi,
|
||||
'Your new modules should be called after your connection to the database'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,107 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e75
|
||||
title: Спілкування за допомогою Emitting
|
||||
challengeType: 2
|
||||
forumTopicId: 301550
|
||||
dashedName: communicate-by-emitting
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
<dfn>Emit</dfn> — найпоширеніший спосіб спілкування, яким ви будете користуватись. Коли ви висвічуєте (emit) щось зі сервера до 'io', ви надсилаєте назву події й дані всім під'єднаним сокетам. Хорошим прикладом цієї концепції було б висвітлення (emitting) поточного рахунку під'єднаних користувачів щоразу коли під'єднується новий користувач!
|
||||
|
||||
Почніть із додавання змінної, щоб відстежувати користувачів, безпосередньо перед тим місцем, де ви зараз слухаєте зв'язки.
|
||||
|
||||
```js
|
||||
let currentUsers = 0;
|
||||
```
|
||||
|
||||
Тепер, коли хтось приєднується, ви повинні збільшити рахунок перш ніж висвітити його. Отже, ви додаєте інкремент в межах слухача підключення.
|
||||
|
||||
```js
|
||||
++currentUsers;
|
||||
```
|
||||
|
||||
Врешті, після збільшення рахунку, ви повинні висвітити подію (все ще в слухачі підключення). Ця подія повинна мати назву "user count", а дані — просто `currentUsers`.
|
||||
|
||||
```js
|
||||
io.emit('user count', currentUsers);
|
||||
```
|
||||
|
||||
Тепер ви можете застосувати цей спосіб для вашого клієнта, аби він міг прослухати цю подію! Подібно до прослуховування з'єднання на сервері, ви будете використовувати ключове слово `on`.
|
||||
|
||||
```js
|
||||
socket.on('user count', function(data) {
|
||||
console.log(data);
|
||||
});
|
||||
```
|
||||
|
||||
Тепер спробуйте завантажити ваш додаток, автентифікуватись і ви повинні побачити на консолі клієнта '1', яка показує поточну кількість користувачів! Спробуйте завантажити більше клієнтів і автентифікуйтеся, щоб побачити, як число зростає.
|
||||
|
||||
Відправте сторінку, якщо все було виконано правильно. Якщо ви зіткнулись з помилкою, ви можете перевірити зроблений проєкт до цього етапу [here](https://gist.github.com/camperbot/28ef7f1078f56eb48c7b1aeea35ba1f5).
|
||||
|
||||
# --hints--
|
||||
|
||||
currentUsers мають бути визначеними.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/currentUsers/gi,
|
||||
'You should have variable currentUsers defined'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Сервер має висвічувати поточний рахунок користувачів в кожному новому зв'язку.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.emit.*('|")user count('|").*currentUsers/gi,
|
||||
'You should emit "user count" with data currentUsers'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ваш клієнт повинен прослухати подію 'user count'.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/gi,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e6a
|
||||
title: Створення нового підпрограмного забезпечення
|
||||
challengeType: 2
|
||||
forumTopicId: 301551
|
||||
dashedName: create-new-middleware
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
На практиці будь-який користувач може просто перейти до `/profile`, незалежно від того, пройшов він автентифікацію чи ні, ввівши Url-адресу. Ми хочемо запобігти цьому, перевіривши, чи користувач автентифікувався, перш ніж відображати сторінку профілю. Це прекрасний приклад того, коли можна створити підпрограмне забезпечення.
|
||||
|
||||
Це завдання завдає функцію підпрограмного забезпечення `ensureAuthenticated(req, res, next)`, яка буде перевіряти чи користувач автентифікувався методом `isAuthenticated` по `request`, який у свою чергу визначає `req.user`. Якщо це так, то слід викликати `next()`, інакше ми можемо просто відповісти на запит перенаправленням на нашу головну сторінку для входу. Впровадження цього підпрограмного забезпечення означає:
|
||||
|
||||
```js
|
||||
function ensureAuthenticated(req, res, next) {
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
}
|
||||
res.redirect('/');
|
||||
};
|
||||
```
|
||||
|
||||
Тепер додайте *ensureAuthenticated* як підпрограмне забезпечення до запиту сторінки профілю перед аргументом, щоб отримати запит, який містить функцію для відображення сторінки.
|
||||
|
||||
```js
|
||||
app
|
||||
.route('/profile')
|
||||
.get(ensureAuthenticated, (req,res) => {
|
||||
res.render(process.cwd() + '/views/pug/profile');
|
||||
});
|
||||
```
|
||||
|
||||
Підтвердьте сторінку, якщо все виконано вірно. Якщо стались помилки, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/ae49b8778cab87e93284a91343da0959).
|
||||
|
||||
# --hints--
|
||||
|
||||
Підпрограмне забезпечення sureAuthenticated має бути реалізованим і на нашому /profile маршруті.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/ensureAuthenticated[^]*req.isAuthenticated/gi,
|
||||
'Your ensureAuthenticated middleware should be defined and utilize the req.isAuthenticated function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/profile[^]*get[^]*ensureAuthenticated/gi,
|
||||
'Your ensureAuthenticated middleware should be attached to the /profile route'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Запит на отримання /profile має правильно переспрямовувати до / оскільки ми не автентифіковані.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/profile').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/gi,
|
||||
'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: 589fc831f9fc0f352b528e76
|
||||
title: Оброблення відключення
|
||||
challengeType: 2
|
||||
forumTopicId: 301552
|
||||
dashedName: handle-a-disconnect
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Ви можете звернути увагу на те, що до цього часу Ви мали тільки збільшення кількості користувачів. Обробка відключення користувача так само легка, як і робота з початковим підключенням, окрім випадків, коли ви повинні слухати його на кожному сокеті, а не на всьому сервері.
|
||||
|
||||
Щоб зробити це, додайте іншого слухача всередині існуючого `'connect'` слухача, який слухає `'disconnect'` на сокеті без обробки даних. Ви можете протестувати ці функції, просто увійшовши до консолі, від якої користувач відключився.
|
||||
|
||||
```js
|
||||
socket.on('disconnect', () => {
|
||||
/*anything you want to do on disconnect*/
|
||||
});
|
||||
```
|
||||
|
||||
Щоб переконатися, що клієнти постійно мають оновлену кількість користувачів, ви повинні зменшити поточних користувачів (currentUsers) на 1, поки відбувається від'єднання потім викликати 'user count' з оновленою кількістю!
|
||||
|
||||
**Примітка:** Як і `'disconnect'`, всі інші події, які сокет може передавати на сервер слід обробити в процесі підключення слухача де ми маємо 'сокет'.
|
||||
|
||||
Підтвердіть вашу сторінку, якщо все зрозуміло. Якщо сталась якась помилка, ви маєте змогу перевірити зроблений проєкт до цього етапу [тут](https://gist.github.com/camperbot/ab1007b76069884fb45b215d3c4496fa).
|
||||
|
||||
# --hints--
|
||||
|
||||
Сервер повинен обробляти подію відключення від сокету.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(data, /socket.on.*('|")disconnect('|")/gi, '');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ваш клієнт повинен слідкувати за 'user count' кодом.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")user count('|")/gi,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,86 @@
|
||||
---
|
||||
id: 58a25c98f9fc0f352b528e7f
|
||||
title: Хешування ваших паролів
|
||||
challengeType: 2
|
||||
forumTopicId: 301553
|
||||
dashedName: hashing-your-passwords
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Якщо ви повернетесь до розділу з інформаційної безпеки, пригадаєте, що зберігання звичайних паролів *не* є гарною ідеєю. Тепер час реалізувати BCrypt для вирішення даної проблеми.
|
||||
|
||||
Додайте `bcrypt@~5.0.0` як залежність та викличте її у своєму сервері. Вам буде потрібно виконати гешування у двох ключових місцях: де ви виконуєте реєстрацію/збереження нового облікового запису і коли ви переконуєтесь, що пароль правильний при вході в систему.
|
||||
|
||||
Наразі під час реєстрації ви вводите пароль користувача у базу даних таким чином: `password: req.body.password`. Легкий спосіб зберегти хеш – це додати перед логікою бази даних: `const hash = bcrypt.hashSync(req.body.password, 12);` та замінити в базі даних `req.body.password`, зберігши лише `password: hash`.
|
||||
|
||||
Зрештою, для нашого способу автентифікації перевіримо наступне в нашому коді перед завершенням процесу: `if (password !== user.password) { return done(null, false); }`. Після внесення попередніх змін тепер `user.password` є хешем. Перш ніж вносити зміни до наявного коду, зверніть увагу на те, як команда перевіряє, чи пароль **не** збігається та потім повертає відповідь — не автентифіковано. Майте це на увазі, ваш код може виглядати наступним чином для правильної перевірки пароля, введеного з хешу:
|
||||
|
||||
```js
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
return done(null, false);
|
||||
}
|
||||
```
|
||||
|
||||
Це все, що потрібно, щоб реалізувати одну з найважливіших функцій безпеки, коли вам потрібно зберігати паролі!
|
||||
|
||||
Підтвердьте вашу сторінку, якщо все зрозуміло. Якщо виникла помилка, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/dc16cca09daea4d4151a9c36a1fab564).
|
||||
|
||||
# --hints--
|
||||
|
||||
BCrypt повинен бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'bcrypt',
|
||||
'Your project should list "bcrypt" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
BCrypt повинен бути правильно заданий та реалізований.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')bcrypt\1/gi,
|
||||
'You should have required bcrypt'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.hashSync/gi,
|
||||
'You should use hash the password in the registration'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/bcrypt.compareSync/gi,
|
||||
'You should compare the password to the hash in your strategy'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: 5895f70ef9fc0f352b528e6b
|
||||
title: Як створити профіль
|
||||
challengeType: 2
|
||||
forumTopicId: 301554
|
||||
dashedName: how-to-put-a-profile-together
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Після того, як ми запевнили, що користувач, який має доступ до `/profile`, автентифікований, ми зможемо використовувати інформацію з `req.user` на нашій сторінці!
|
||||
|
||||
Передайте об'єкт, що має властивість `username` і значення `req.user.username` як другий аргумент для методу відображення профілю. Тепер перейдіть до перегляду свого `profile.pug` і додайте наступний рядок під уже наявним `h1` елементом, дотримуючись тих самих відступів:
|
||||
|
||||
```pug
|
||||
h2.center#welcome Welcome, #{username}!
|
||||
```
|
||||
|
||||
Це створить елемент `h2` з класом '`center`' і id '`welcome`', що міститиме текст '`Welcome,`' та ім'я користувача.
|
||||
|
||||
Також в `profile.pug` додайте посилання на маршрут `/logout`, який міститиме метод для скасування автентифікації користувача.
|
||||
|
||||
```pug
|
||||
a(href='/logout') Logout
|
||||
```
|
||||
|
||||
Відправте сторінку, якщо все було виконано правильно. Якщо ви натрапили на помилки, ви можете перевірити виконання проєкту до цього етапу [here](https://gist.github.com/camperbot/136b3ad611cc80b41cab6f74bb460f6a).
|
||||
|
||||
# --hints--
|
||||
|
||||
Ви повинні правильно додати змінну Pug до /profile.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/username:( |)req.user.username/gi,
|
||||
'You should be passing the variable username with req.user.username into the render function of the profile page'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: 5895f70df9fc0f352b528e69
|
||||
title: Як використовувати Passport стратегії
|
||||
challengeType: 2
|
||||
forumTopicId: 301555
|
||||
dashedName: how-to-use-passport-strategies
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
У заданому файлі `index.pug` є форма авторизації. Її раніше було приховано через вбудований JavaScript з формою `if showLogin` та відступом після неї. Перед `showLogin` у вигляді змінної яка ніколи не була визначена, тому вона ніколи не передавала блок коду, що містить форму. Перейдіть вперед і на `res.render` для цієї сторінки додайте нову змінну до об'єкта `showLogin: true`. Коли ви оновите сторінку, ви повинні побачити форму! Ця форма налаштована для надсилання **POST** запиту до `/login`, тому тут ми повинні прийняти POST і авторизувати користувача.
|
||||
|
||||
Для цього завдання необхідно додати маршрут `/login`, щоб прийняти запит POST. Для автентифікації на цьому маршруті, вам потрібно додати підпрограмне забезпечення, щоб зробити це перед тим як надсилати відповідь. Це можна зробити простим передаванням іншого аргументу з підпрограмного забезпечення перед `function(req,res)` з вашою відповіддю! Треба використати таке підпрограмне забезпечення: `passport.authenticate('local')`.
|
||||
|
||||
`passport.authenticate` може також приймати деякі опції як аргумент: `{ failureRedirect: '/' }`, який є неймовірно корисними, тому будьте впевнені в тому, що також додали і його. Відповідь після використання підпрограмного забезпечення (яка буде викликана якщо автентифікація підпрограмного забезпечення виконана успішно) повинна переадресувати користувача до `/profile` і цей маршрут повинен зобразити вид `profile.pug`.
|
||||
|
||||
Якщо автентифікація була успішною, то об'єкт користувач буде збережено в `req.user`.
|
||||
|
||||
Зараз, якщо ви введете ім'я користувача і пароль у форму, вас має переадресувати на домашню сторінку `/`, а консоль вашого серверу повинна показувати `'User {USERNAME} attempted to log in.'`, оскільки ми на цей час не можемо увійти в обліковий запис користувача, який не зареєстрований.
|
||||
|
||||
Підтвердіть сторінку, якщо все зрозуміло. Якщо сталась якась помилка, ви маєте змогу перевірити статус проєкту до цього етапу [here](https://gist.github.com/camperbot/7ad011ac54612ad53188b500c5e99cb9).
|
||||
|
||||
# --hints--
|
||||
|
||||
Усі кроки повинні бути правильно реалізовані в server.js.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/showLogin:( |)true/gi,
|
||||
'You should be passing the variable "showLogin" as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/failureRedirect:( |)('|")\/('|")/gi,
|
||||
'Your code should include a failureRedirect to the "/" route'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/login[^]*post[^]*local/gi,
|
||||
'You should have a route for login which accepts a POST and passport.authenticates local'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Запит POST для /login повинен правильно переадресовувати до /.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.post(getUserInput('url') + '/login').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Looks like this page is being rendered from Pug into HTML!/gi,
|
||||
'A login attempt at this point should redirect to the homepage since we do not have any registered users'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,91 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e67
|
||||
title: Впровадження Серіалізації Користувача у Паспорт
|
||||
challengeType: 2
|
||||
forumTopicId: 301556
|
||||
dashedName: implement-the-serialization-of-a-passport-user
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Зараз ми не завантажуємо поточний об’єкт користувача, оскільки ми ще не налаштували нашу базу даних. Це можна зробити багатьма різними способами, але для нашого проєкту ми під'єднаємось до бази даних один раз при запуску сервера і збережемо постійне з'єднання протягом усього життєвого циклу додатку. Для цього додайте рядок з'єднання вашої бази даних (наприклад: `mongodb+srv://:@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority`) до змінної середовища `MONGO_URI`. Це використовується у файлі `connection.js`.
|
||||
|
||||
*Ви можете створити безкоштовну базу даних на [MongoDB Atlas](https://www.mongodb.com/cloud/atlas).*
|
||||
|
||||
Тепер ми хочемо підключитися до нашої бази даних, а після того почати створювати запити. Це робимо з метою, щоб заборонити запити до того, як наша база даних буде підключена або якщо у неї виникне помилка. Для досягнення цього вам потрібно включити свою серіалізацію та маршрути додатків у такий код:
|
||||
|
||||
```js
|
||||
myDB(async client => {
|
||||
const myDataBase = await client.db('database').collection('users');
|
||||
|
||||
// Be sure to change the title
|
||||
app.route('/').get((req, res) => {
|
||||
//Change the response to render the Pug template
|
||||
res.render('pug', {
|
||||
title: 'Connected to Database',
|
||||
message: 'Please login'
|
||||
});
|
||||
});
|
||||
|
||||
// Serialization and deserialization here...
|
||||
|
||||
// Be sure to add this...
|
||||
}).catch(e => {
|
||||
app.route('/').get((req, res) => {
|
||||
res.render('pug', { title: e, message: 'Unable to login' });
|
||||
});
|
||||
});
|
||||
// app.listen out here...
|
||||
```
|
||||
|
||||
Не забудьте розкоментувати код `myDataBase` в `deserializeUser` та відредагувати свій `done(null, null)` для того, щоб включити `doc`.
|
||||
|
||||
Відправте сторінку, якщо все було виконано правильно. Якщо ви зіткнулись з помилкою, ви можете перевірити зроблений проєкт до цього етапу [тут](https://gist.github.com/camperbot/175f2f585a2d8034044c7e8857d5add7).
|
||||
|
||||
# --hints--
|
||||
|
||||
Повинно бути присутнім з'єднання з базою даних.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Connected to Database/gi,
|
||||
'You successfully connected to the database!'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Тепер десеріалізація повинна виконуватися правильно у БД, а `done(null, null)` повинна викликатися із `doc`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*doc/gi,
|
||||
'The callback in deserializeUser of (null, null) should be altered to (null, doc)'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,114 @@
|
||||
---
|
||||
id: 589a69f5f9fc0f352b528e71
|
||||
title: Реалізація соціальної автентифікації ІІ
|
||||
challengeType: 2
|
||||
forumTopicId: 301557
|
||||
dashedName: implementation-of-social-authentication-ii
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Остання частина налаштування автентифікації GitHub – це створення самої стратегії. Для цього вам потрібно буде додати залежність від `passport-github@~1.1.0` до вашого проєкту та викликати її у своєму `auth.js` як `GithubStrategy`, це: `const GitHubStrategy = require('passport-github').Strategy;`. Не забудьте викликати та налаштовувати `dotenv` для використання змінних середовища.
|
||||
|
||||
Щоб налаштувати стратегію GitHub, ви повинні сказати Passport використовувати примірник `GitHubStrategy`, який приймає 2 аргументи: об'єкт (який містить `clientID`, `clientSecret` та `callbackURL`) та функцію, яку слід викликати після успішної автентифікації користувача, яка визначатиме, чи є користувач новим і які поля спочатку зберегти в об’єкті бази даних користувача. Це типово для багатьох стратегій, але деяким може знадобитися більше інформації, як зазначено в цій конкретній стратегії GitHub README. Наприклад, так Google вимагає *scope*, що визначає, яку інформацію просить повернути ваш запит, і просить користувача надати дозвіл на такий доступ. Використання поточної стратегії, яку ми впроваджуємо описане [тут](https://github.com/jaredhanson/passport-github/), але ми все це розглянемо тут, на freeCodeCamp!
|
||||
|
||||
Ось як ваша нова стратегія має виглядати на цьому етапі:
|
||||
|
||||
```js
|
||||
passport.use(new GitHubStrategy({
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL: /*INSERT CALLBACK URL ENTERED INTO GITHUB HERE*/
|
||||
},
|
||||
function(accessToken, refreshToken, profile, cb) {
|
||||
console.log(profile);
|
||||
//Database logic here with callback containing our user object
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
Ваша автентифікація ще не є успішною, і насправді видасть помилку без логіки бази даних та зворотного виклику, але вона повинна зареєструвати ваш профіль GitHub на вашій консолі, якщо ви це спробуєте!
|
||||
|
||||
Підтвердьте сторінку, якщо все зрозуміло. Якщо виникла помилка, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/ff3a1166684c1b184709ac0bee30dee6).
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно додати залежність passport-github.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport-github',
|
||||
'Your project should list "passport-github" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Слід вказати passport-github.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/auth.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport-github("|')/gi,
|
||||
'You should have required passport-github'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Стратегію GitHub слід правильно налаштувати.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/auth.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport\.use.*new GitHubStrategy/gis,
|
||||
'Passport should use a new GitHubStrategy'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/callbackURL:\s*("|').*("|')/gi,
|
||||
'You should have a callbackURL'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_SECRET|\[(?<q>"|')GITHUB_CLIENT_SECRET\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_SECRET'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/process\.env(\.GITHUB_CLIENT_ID|\[(?<q>"|')GITHUB_CLIENT_ID\k<q>\])/g,
|
||||
'You should use process.env.GITHUB_CLIENT_ID'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,80 @@
|
||||
---
|
||||
id: 589a8eb3f9fc0f352b528e72
|
||||
title: Реалізація соціальної автентифікації ІІІ
|
||||
challengeType: 2
|
||||
forumTopicId: 301558
|
||||
dashedName: implementation-of-social-authentication-iii
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Кінцева частина стратегії керує профілем поверненим з GitHub. Якщо існує об'єкт бази даних користувача, необхідно його завантажити. У разі відсутності, потрібно його створити. Потім заповнюємо поля профілю і повертаємо об'єкт користувача. У межах кожного профілю GitHub надає нам унікальний *id*, який можна використовувати для пошуку для того, щоб серіалізувати користувача (уже реалізовано). Нижче наведено приклад реалізації, який ви можете використовувати у вашому проєкті - це відбувається завдяки функції, яка є другим аргументом для нової стратегії, нижче де `console.log(profile);` наразі є:
|
||||
|
||||
```js
|
||||
myDataBase.findOneAndUpdate(
|
||||
{ id: profile.id },
|
||||
{
|
||||
$setOnInsert: {
|
||||
id: profile.id,
|
||||
name: profile.displayName || 'John Doe',
|
||||
photo: profile.photos[0].value || '',
|
||||
email: Array.isArray(profile.emails)
|
||||
? profile.emails[0].value
|
||||
: 'No public email',
|
||||
created_on: new Date(),
|
||||
provider: profile.provider || ''
|
||||
},
|
||||
$set: {
|
||||
last_login: new Date()
|
||||
},
|
||||
$inc: {
|
||||
login_count: 1
|
||||
}
|
||||
},
|
||||
{ upsert: true, new: true },
|
||||
(err, doc) => {
|
||||
return cb(null, doc.value);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
`findOneAndUpdate` дозволяє шукати та оновлювати об'єкт. У разі, якщо об'єкта не існує, його вставлять та зроблять доступним для функції зворотнього зв'язку. У цьому прикладі, ми завжди встановлюємо `last_login`, збільшуємо `login_count` на `1` та після додавання нового об'єкту (користувача) лише заповнюємо більшість полів. Зверніть увагу на використання значень за замовчуванням. Іноді повернений профіль не міститиме усієї інформації або ж користувач зробить її приватною. У такому випадку, ви його обробляєте задля уникнення помилки.
|
||||
|
||||
Ви повинні мати змогу увійти в ваш додаток прямо зараз — спробуйте!
|
||||
|
||||
Підтвердьте сторінку, якщо все виконано вірно. Якщо виникла помилка, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/183e968f0e01d81dde015d45ba9d2745).
|
||||
|
||||
# --hints--
|
||||
|
||||
Ви повинні завершити налаштування стратегії GitHub.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/auth.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*myDataBase/gi,
|
||||
'Strategy should use now use the database to search for the user'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/GitHubStrategy[^]*return cb/gi,
|
||||
'Strategy should return the callback function "cb"'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,108 @@
|
||||
---
|
||||
id: 589a69f5f9fc0f352b528e70
|
||||
title: Реалізація соціальної автентифікації
|
||||
challengeType: 2
|
||||
forumTopicId: 301559
|
||||
dashedName: implementation-of-social-authentication
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Основний шлях автентифікації відстежуватиметься у вашому застосунку:
|
||||
|
||||
1. Користувач натисне кнопку або з'єднається з нашим маршрутом для автентифікації у визначеній стратегії (наприклад, GitHub).
|
||||
2. Ваш маршрут `passport.authenticate('github')` перенаправляє їх на GitHub.
|
||||
3. Сторінка, на яку користувач заходить, на GitHub, дозволяє йому увійти в систему, якщо він ще не зробив це. Система попросить підтвердити доступ до свого профілю з нашого застосунку.
|
||||
4. Потім користувач повертається до нашого застосунку за адресою зворотного виклику url з його профілем, якщо він схвалений.
|
||||
5. Тепер вони аутентифіковані, і ваш застосунок перевірить, чи це постійний профіль, або збереже його у вашій базі даних, якщо це не так.
|
||||
|
||||
Стратегії з OAuth потребують, щоб ви мали принаймні *Client ID* та *Client Secret*, так сервіс перевірить, від кого надходить запит автентифікації та чи є він дійсним. Вони отримані з сайту, з яким ви намагаєтеся здійснити автентифікацію, наприклад, з сайту GitHub і вони є унікальними для вашого застосунку **ВОНИ НЕ Є ДЛЯ ЗАГАЛЬНОГО ДОСТУПУ** і ніколи не повинні бути завантажені в публічне сховище або написані безпосередньо у вашому коді. Поширеною практикою є розміщення їх у файлі `.env` і посилання на них таким чином: `process.env.GITHUB_CLIENT_ID`. Для вирішення цього завдання ми будемо використовувати стратегію GitHub.
|
||||
|
||||
Отримання вашого *Client ID and Secret* від GitHub можна зробити в налаштуваннях профілю вашого облікового запису в розділі "Налаштування розробника", потім '[OAuth applications](https://github.com/settings/developers)'. Натисніть "Зареєструвати нову програму", назвіть свій додаток, вставте URL-адресу на свою домашню сторінку Replit (**Not the project code's url**), і, зрештою, для URL-адреси зворотного виклику, вставте ту саму URL-адресу як і домашню сторінку, але з додаванням `/auth/github/callback`. Сюди користувачі будуть переадресовані для обробки після автентифікації на GitHub. Збережіть отриману інформацію як `'GITHUB_CLIENT_ID'` та `'GITHUB_CLIENT_SECRET'` у вашому файлі `.env`.
|
||||
|
||||
У вашому файлі `routes.js` додайте `showSocialAuth: true` до маршруту головної сторінки після `showRegistration: true`. Тепер створіть 2 маршрути, які прийматимуть запити GET: `/auth/github` та `/auth/github/callback`. Перший повинен лише викликати паспорт для автентифікації `'github'`. Другий повинен викликати паспорт для автентифікації `'github'` з помилкою переадресації на `/`, а потім, якщо вдалося, переадресувати на `/profile` (подібно до нашого останнього проєкту).
|
||||
|
||||
Приклад того, що `/auth/github/callback` має виглядати подібним до обробки звичного логіну:
|
||||
|
||||
```js
|
||||
app.route('/login')
|
||||
.post(passport.authenticate('local', { failureRedirect: '/' }), (req,res) => {
|
||||
res.redirect('/profile');
|
||||
});
|
||||
```
|
||||
|
||||
Підтвердьте сторінку, якщо все зрозуміло. Якщо сталась якась помилка, ви маєте змогу перевірити статус проєкту до цього етапу [тут](https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e).
|
||||
|
||||
# --hints--
|
||||
|
||||
Шлях `/auth/github` має бути правильним.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const res = await fetch(getUserInput('url') + '/_api/routes.js');
|
||||
if (res.ok) {
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data.replace(/\s/g, ''),
|
||||
/passport.authenticate.*?github/g,
|
||||
'Route auth/github should only call passport.authenticate with github'
|
||||
);
|
||||
} else {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const res2 = await fetch(getUserInput('url') + '/_api/app-stack');
|
||||
if (res2.ok) {
|
||||
const data2 = JSON.parse(await res2.json());
|
||||
const dataLayer = data2.find(layer => layer?.route?.path === '/auth/github');
|
||||
assert.deepInclude(dataLayer?.route, { methods: {get: true}, path: "/auth/github"});
|
||||
assert.deepInclude(dataLayer?.route?.stack?.[0], {method: "get", name: "authenticate"});
|
||||
} else {
|
||||
throw new Error(res2.statusText);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Шлях `/auth/github/callback` повинен бути правильним.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const res = await fetch(getUserInput('url') + '/_api/routes.js');
|
||||
if (res.ok) {
|
||||
const data = await res.text();
|
||||
assert.match(
|
||||
data.replace(/\s/g, ''),
|
||||
/failureRedirect:("|')\/\1/g,
|
||||
'Route auth/github/callback should accept a get request and call passport.authenticate for github with a failure redirect to home'
|
||||
);
|
||||
} else {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const res2 = await fetch(getUserInput('url') + '/_api/app-stack');
|
||||
if (res2.ok) {
|
||||
const data2 = JSON.parse(await res2.json());
|
||||
const dataLayer = data2.find(layer => layer?.route?.path === '/auth/github/callback');
|
||||
assert.deepInclude(dataLayer?.route, { methods: {get: true}, path: "/auth/github/callback"});
|
||||
assert.deepInclude(dataLayer?.route?.stack?.[0], {method: "get", name: "authenticate"});
|
||||
} else {
|
||||
throw new Error(res2.statusText);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: 58965611f9fc0f352b528e6c
|
||||
title: Вихід з облікового запису
|
||||
challengeType: 2
|
||||
forumTopicId: 301560
|
||||
dashedName: logging-a-user-out
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Логіка logout легко створюється. Маршрут має лише скасувати автентифікацію користувача та переадресувати на головну сторінку замість візуалізації будь-якого перегляду.
|
||||
|
||||
Скасувати автентифікацію користувача через Passport так само легко, як викликати `req.logout();` до початку переспрямування.
|
||||
|
||||
```js
|
||||
app.route('/logout')
|
||||
.get((req, res) => {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
});
|
||||
```
|
||||
|
||||
Певно, ви помітили, що ми не обробляємо відсутні вебсторінки (404). Поширений спосіб обробки цієї помилки у Node є наступне проміжне програмне забезпечення. Додайте це після всіх інших маршрутів:
|
||||
|
||||
```js
|
||||
app.use((req, res, next) => {
|
||||
res.status(404)
|
||||
.type('text')
|
||||
.send('Not Found');
|
||||
});
|
||||
```
|
||||
|
||||
Підтвердьте сторінку, якщо все виконано вірно. Якщо виникла помилка, Ви можете перевірити виконаний до цього етапу проєкт [тут](https://gist.github.com/camperbot/c3eeb8a3ebf855e021fd0c044095a23b).
|
||||
|
||||
# --hints--
|
||||
|
||||
`req.Logout` має викликатися у маршруті `/logout`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/req.logout/gi,
|
||||
'You should be calling req.logout() in your /logout route'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Logout має переадресовувати на головну сторінку.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/logout').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Home page/gi,
|
||||
'When a user logs out they should be redirected to the homepage'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,205 @@
|
||||
---
|
||||
id: 58966a17f9fc0f352b528e6d
|
||||
title: Реєстрація нових користувачів
|
||||
challengeType: 2
|
||||
forumTopicId: 301561
|
||||
dashedName: registration-of-new-users
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Тепер потрібно дозволити новому користувачеві створити обліковий запис на нашому сайті. У `res.render` для головної сторінки додайте нову змінну до переданого об'єкта `showRegistration: true`. Після оновлення вашої сторінки, ви побачите реєстраційну форму, яка вже була створена в файлі `index.pug`! Ця форма налаштована для надсилання **POST** запиту до `/register`, тому тут ми повинні прийняти **POST** і створити об'єкт користувача в базі даних.
|
||||
|
||||
Логіка реєстраційного маршруту має бути наступною: Реєстрація нового користувача > Автентифікація нового користувача > Переадресація на /profile
|
||||
|
||||
Логіка кроку 1, реєстрація нового користувача, повинна бути такою: Зробіть запит до бази даних за допомогою команди findOne > Якщо користувач був повернений, то він існує і переадресовується на головну сторінку *АБО* якщо користувач не визначений і помилки не виникло, тоді 'insertOne' у базу даних з ім'ям користувача та паролем, і поки не з'явиться помилок, викликайте *наступного*, щоб перейти до кроку 2, автентифікації нового користувача, для якого вже написана логіка в маршруті POST */login*.
|
||||
|
||||
```js
|
||||
app.route('/register')
|
||||
.post((req, res, next) => {
|
||||
myDataBase.findOne({ username: req.body.username }, function(err, user) {
|
||||
if (err) {
|
||||
next(err);
|
||||
} else if (user) {
|
||||
res.redirect('/');
|
||||
} else {
|
||||
myDataBase.insertOne({
|
||||
username: req.body.username,
|
||||
password: req.body.password
|
||||
},
|
||||
(err, doc) => {
|
||||
if (err) {
|
||||
res.redirect('/');
|
||||
} else {
|
||||
// The inserted document is held within
|
||||
// the ops property of the doc
|
||||
next(null, doc.ops[0]);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
passport.authenticate('local', { failureRedirect: '/' }),
|
||||
(req, res, next) => {
|
||||
res.redirect('/profile');
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте сторінку, якщо все виконано вірно. Якщо сталась якась помилка, ви маєте змогу перевірити зроблений проєкт до цього етапу [тут](https://gist.github.com/camperbot/b230a5b3bbc89b1fa0ce32a2aa7b083e).
|
||||
|
||||
**ПРИМІТКА:** З цього моменту можуть виникнути проблеми з використанням браузеру *picture-in-picture*. Якщо ви використовуєте онлайн IDE, який має попередній перегляд програми в редакторі, рекомендується відкрити цей попередній перегляд у новій вкладці.
|
||||
|
||||
# --hints--
|
||||
|
||||
Ви повинні зареєструвати маршрут та відобразити його на головній сторінці.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/showRegistration:( |)true/gi,
|
||||
'You should be passing the variable showRegistration as true to your render function for the homepage'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi,
|
||||
'You should have a route accepted a post request on register that querys the db with findone and the query being username: req.body.username'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Реєстрація повинна працювати.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
const url = getUserInput('url');
|
||||
const user = `freeCodeCampTester${Date.now()}`;
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
test(this);
|
||||
} else {
|
||||
throw new Error(`${this.status} ${this.statusText}`);
|
||||
}
|
||||
};
|
||||
xhttp.open('POST', url + '/register', true);
|
||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhttp.send(`username=${user}&password=${user}`);
|
||||
function test(xhttpRes) {
|
||||
const data = xhttpRes.responseText;
|
||||
assert.match(
|
||||
data,
|
||||
/Profile/gi,
|
||||
'Register should work, and redirect successfully to the profile.'
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Вхід в обліковий запис повинен працювати.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
const url = getUserInput('url');
|
||||
const user = `freeCodeCampTester${Date.now()}`;
|
||||
const xhttpReg = new XMLHttpRequest();
|
||||
xhttpReg.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
login();
|
||||
} else {
|
||||
throw new Error(`${this.status} ${this.statusText}`);
|
||||
}
|
||||
};
|
||||
xhttpReg.open('POST', url + '/register', true);
|
||||
xhttpReg.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhttpReg.send(`username=${user}&password=${user}`);
|
||||
function login() {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
test(this);
|
||||
} else {
|
||||
throw new Error(`${this.status} ${this.statusText}`);
|
||||
}
|
||||
};
|
||||
xhttp.open('POST', url + '/login', true);
|
||||
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xhttp.send(`username=${user}&password=${user}`);
|
||||
}
|
||||
function test(xhttpRes) {
|
||||
const data = xhttpRes.responseText;
|
||||
assert.match(
|
||||
data,
|
||||
/Profile/gi,
|
||||
'Login should work if previous test was done successfully and redirect successfully to the profile.'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
new RegExp(user, 'g'),
|
||||
'The profile should properly display the welcome to the user logged in'
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Вихід з облікового запису повинен працювати.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.ajax({
|
||||
url: getUserInput('url') + '/logout',
|
||||
type: 'GET',
|
||||
xhrFields: { withCredentials: true }
|
||||
}).then(
|
||||
(data) => {
|
||||
assert.match(data, /Home/gi, 'Logout should redirect to home');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Профіль більше не повинен працювати після виходу з облікового запису.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.ajax({
|
||||
url: getUserInput('url') + '/profile',
|
||||
type: 'GET',
|
||||
crossDomain: true,
|
||||
xhrFields: { withCredentials: true }
|
||||
}).then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/Home/gi,
|
||||
'Profile should redirect to home when we are logged out now again'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: 589fc832f9fc0f352b528e79
|
||||
title: Відправлення та відображення повідомлень чату
|
||||
challengeType: 2
|
||||
forumTopicId: 301562
|
||||
dashedName: send-and-display-chat-messages
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Настав час почати надсилати повідомлення чату на сервер всім клієнтам! У файлі `client.js`, ви бачите, що вже існує блок обробки коду, після відправлення форми повідомлення.
|
||||
|
||||
```js
|
||||
$('form').submit(function() {
|
||||
/*logic*/
|
||||
});
|
||||
```
|
||||
|
||||
У межах коду відправлення форми ви повинні видати (emit) подію після визначення `messageToSend`, але перед тим, як ви очистите текстову панель `#m`. Код повинен називатися `'chat message'` і дані повинні бути `messageToSend`.
|
||||
|
||||
```js
|
||||
socket.emit('chat message', messageToSend);
|
||||
```
|
||||
|
||||
Тепер на вашому сервері, ви мусите прослухати сокет для події `'chat message'` з назвою `message`. Після отримання події він повинен буде видати подію `'chat message'` до всіх сокетів `io.emit` з даними об'єкта, що містить `name` та `message`.
|
||||
|
||||
В `client.js`, тепер необхідно послухати подію `'chat message'` і після отримання, додати список елементів до `#message` з іменем, двокрапкою та повідомленням!
|
||||
|
||||
На даний момент чат повинен бути повністю функціональним і спроможним відправляти повідомлення всім клієнтам!
|
||||
|
||||
Підтвердіть вашу сторінку, якщо все зрозуміло. Якщо сталась якась помилка, ви маєте змогу перевірити зроблений проєкт до цього етапу [тут](https://gist.github.com/camperbot/d7af9864375207e254f73262976d2016).
|
||||
|
||||
# --hints--
|
||||
|
||||
Сервер має слухати `'chat message'` та переміщувати (emit) його належним чином.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*name.*message/gis,
|
||||
'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'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Клієнт повинен правильно обробляти та показувати нові дані із події `'chat message'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.on.*('|")chat message('|")[^]*messages.*li/gis,
|
||||
'You should append a list item to #messages on your client within the "chat message" event listener to display the new message'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,131 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e66
|
||||
title: Серіалізація об'єкта користувача
|
||||
challengeType: 2
|
||||
forumTopicId: 301563
|
||||
dashedName: serialization-of-a-user-object
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Серіалізація та десеріалізація є важливими поняттями щодо автентифікації. Серіалізація об’єкта означає перетворення його вмісту в маленький *ключ*, який потім можна десеріалізувати у вихідний об’єкт. Це дозволяє нам знати, хто користувався сервером без надсилання даних автентифікації, таких як ім’я користувача та пароль, при кожному запиті на нову сторінку.
|
||||
|
||||
Щоб налаштувати його належним чином, потрібно мати функцію серіалізації та функцію десеріалізації. У Passport, ми створюємо їх за допомогою `passport.serializeUser( OURFUNCTION )` та `passport.deserializeUser( OURFUNCTION )`
|
||||
|
||||
`serializeUser` викликається 2 аргументами, повним об'єктом користувача та зворотним викликом, що використовується паспортом. Унікальний ключ для ідентифікації того, що користувача слід повернути у зворотному виклику, найпростіший у використанні `_id` користувача в об'єкті. Він повинен бути унікальним, оскільки він створений MongoDB. Аналогічно, `deserializeUser` викликається за допомогою цього ключа і також функції зворотного виклику паспорта, але цього разу ми повинні взяти цей ключ і повернути повний об'єкт користувача до зворотного виклику. Щоб здійснити пошук за запитом для Mongo `_id`, вам потрібно створити `const ObjectID = require('mongodb').ObjectID;`, а потім, щоб використати його, викличте `new ObjectID(THE_ID)`. Обов’язково додайте `mongodb@~3.6.0` як залежність. Ви можете побачити це в наведених нижче прикладах:
|
||||
|
||||
```js
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user._id);
|
||||
});
|
||||
|
||||
passport.deserializeUser((id, done) => {
|
||||
myDataBase.findOne({ _id: new ObjectID(id) }, (err, doc) => {
|
||||
done(null, null);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
ПРИМІТКА: `deserializeUser` видасть помилку, поки ми не налаштуємо базу даних (DB) на наступному кроці, тому поки що прокоментуйте весь блок і просто викличте `done(null, null)` у функції `deserializeUser`.
|
||||
|
||||
Підтвердіть сторінку, якщо все виконано вірно. Якщо виникла помилка, ви можете перевірити виконання проєкту до цього етапу [here](https://gist.github.com/camperbot/7068a0d09e61ec7424572b366751f048).
|
||||
|
||||
# --hints--
|
||||
|
||||
Вам слід правильно серіалізувати функцію користувача.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport.serializeUser/gi,
|
||||
'You should have created your passport.serializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*user._id/gi,
|
||||
'There should be a callback in your serializeUser with (null, user._id)'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Вам слід належним чином десеріалізувати функцію користувача.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport.deserializeUser/gi,
|
||||
'You should have created your passport.deserializeUser function'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/null,\s*null/gi,
|
||||
'There should be a callback in your deserializeUser with (null, null) for now'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
MongoDB має бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'mongodb',
|
||||
'Your project should list "mongodb" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Mongodb повинен бути включеним (required) належним чином, включаючи ObjectId.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')mongodb\1/gi,
|
||||
'You should have required mongodb'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/new ObjectID.*id/gi,
|
||||
'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,116 @@
|
||||
---
|
||||
id: 5895f700f9fc0f352b528e63
|
||||
title: Налаштування шаблону двигуна
|
||||
challengeType: 2
|
||||
forumTopicId: 301564
|
||||
dashedName: set-up-a-template-engine
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Робота над цими завданнями передбачає написання коду одним із таких методів:
|
||||
|
||||
- Клонуйте [ цей GitHub репозиторій](https://github.com/freeCodeCamp/boilerplate-advancednode/) і локально виконайте ці завдання.
|
||||
- Використовуйте [наш стартовий проєкт Replit](https://replit.com/github/freeCodeCamp/boilerplate-advancednode) для виконання цих завдань.
|
||||
- Використовуйте конструктор сайтів на ваш розсуд для завершення проекту. Впевніться, що ви маєте усі файли з нашого GitHub репозиторію.
|
||||
|
||||
По завершенню, переконайтеся, що демоверсія вашого проєкту розміщена у відкритому доступі. Потім введіть URL-адресу проєкту у поле `Solution Link`.
|
||||
|
||||
Шаблон двигуна дозволяє використовувати статичні шаблони файлів (такі як написані в *Pug*) у вашому додатку. У той час шаблон двигуна замінює варіації у файлі шаблону фактичними значеннями, які можуть надаватися вашим сервером. Потім він перетворює шаблон в статичний HTML файл, який надсилається клієнту. Цей підхід спрощує дизайн HTML сторінки та дозволяє показувати змінні на сторінці без необхідності виклику API від клієнта.
|
||||
|
||||
Додайте `pug@~3.0.0` в якості залежності у файлі `package.json`.
|
||||
|
||||
Висловіть свої потреби для розуміння, який шаблон двигуна ви використовуєте. Ми будемо використовувати метод `set` для призначення `pug` як `view engine` значення властивості: `app.set('view engine', 'pug')`
|
||||
|
||||
Ваша сторінка не буде завантажуватися, поки ви не зобразите правильно індексний файл у каталозі `views/pug`.
|
||||
|
||||
Змініть аргумент `res.render()` оголошення в маршруті `/` як шлях до файлу каталогу `views/pug`. Шлях може бути відносним шляхом (відносно переглядів) або абсолютним шляхом і не потребує розширення файлу.
|
||||
|
||||
Якщо все пройшло по плану, ваша домашня сторінка додатку зупинить показ повідомлення "`Pug template is not defined.`" і тепер буде показано повідомлення, яке вказує, що ви успішно зобразили шаблон Pug!
|
||||
|
||||
Підтвердіть свою сторінку, коли зрозумієте, що все працює коректно. Якщо виникла помилка, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/3515cd676ea4dfceab4e322f59a37791).
|
||||
|
||||
# --hints--
|
||||
|
||||
Pug повинен бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'pug',
|
||||
'Your project should list "pug" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Двигун перегляду має бути Pug.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/('|")view engine('|"),( |)('|")pug('|")/gi,
|
||||
'Your project should set Pug as a view engine'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Використайте правильний метод ExpressJS, щоб відобразити сторінку індексу з відповіді.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/FCC Advanced Node and Express/gi,
|
||||
'You successfully rendered the Pug template!'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Pug має працювати.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/pug-success-message/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,132 @@
|
||||
---
|
||||
id: 5895f70cf9fc0f352b528e65
|
||||
title: Налаштування Passport
|
||||
challengeType: 2
|
||||
forumTopicId: 301565
|
||||
dashedName: set-up-passport
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Час встановити *Passport*, щоб нарешті дозволити користувачеві реєструватися або входити в обліковий запис! Разом з Passport, ми будемо використовувати експрес-сесію (Express-session) для обробки сесій. Використання цього проміжного програмного забезпечення зберігає id сесії як файл cookie в клієнті та дозволяє нам отримувати доступ до даних сесії за допомогою цього id на сервері. Таким чином ми зберігаємо особисту інформацію облікового запису поза файлом cookie, який клієнт використовує для перевірки на нашому сервері їх автентичності, і просто зберігаємо *ключ* для доступу до даних, що зберігаються на сервері.
|
||||
|
||||
Щоб налаштувати Passport для використання в своєму проекті, вам потрібно буде спочатку додати його як залежність у вашому package.json. `passport@~0.4.1`
|
||||
|
||||
Крім того, додайте також експрес-сесію як залежність. Експрес-сесія має дуже великий набір розширених функцій, які ви можете використати, але зараз ми збираємося використовувати лише базові функції! `express-session@~1.17.1`
|
||||
|
||||
Зараз вам потрібно буде встановити налаштування сесії та ініціалізувати Passport. Переконайтеся, що створили змінні 'session' та 'passport', це потрібно для 'express-session' та 'passport'.
|
||||
|
||||
Щоб налаштувати експрес-застосунок для використання у сесії, ми визначимо лише декілька основних параметрів. Не забудьте додати 'SESSION_SECRET' до вашого .env файлу і надати йому випадкове значення. Це використовується для обчислення хешу, яке використовується для шифрування вашого cookie!
|
||||
|
||||
```js
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: false }
|
||||
}));
|
||||
```
|
||||
|
||||
Крім того, ви можете продовжити та повідомити своєму експрес-застосунку **використовувати** 'Passpor.initialize()' та 'Pasort.session()'. (Наприклад, `app.use(passport.initialize());`)
|
||||
|
||||
Відправте сторінку, якщо все було виконано правильно. Якщо виникають помилки, ви можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/4068a7662a2f9f5d5011074397d6788c).
|
||||
|
||||
# --hints--
|
||||
|
||||
Passport і експрес-сесії повинні бути залежностями.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'passport',
|
||||
'Your project should list "passport" as a dependency'
|
||||
);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'express-session',
|
||||
'Your project should list "express-session" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Пов'язані залежності повинні бути правильно заповнені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')passport("|')/gi,
|
||||
'You should have required passport'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/require.*("|')express-session("|')/gi,
|
||||
'You should have required express-session'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Експрес застосунок має використовувати нові залежності.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/passport.initialize/gi,
|
||||
'Your express app should use "passport.initialize()"'
|
||||
);
|
||||
assert.match(
|
||||
data,
|
||||
/passport.session/gi,
|
||||
'Your express app should use "passport.session()"'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Сесія і секрет сесії мають бути правильно налаштовані.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/secret *: *process\.env(\.SESSION_SECRET|\[(?<q>"|')SESSION_SECRET\k<q>\])/g,
|
||||
'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,148 @@
|
||||
---
|
||||
id: 589fc830f9fc0f352b528e74
|
||||
title: Налаштування середовища
|
||||
challengeType: 2
|
||||
forumTopicId: 301566
|
||||
dashedName: set-up-the-environment
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Наступні завдання використовуватимуть файл `chat.pug`. Отже, у файлі `routes.js` додайте шлях GET, вказавши на `/chat`, що використовує `ensureAuthenticated` і відображає `chat.pug`, передаючи `{ user: req.user }` як аргумент до відповіді. Тепер змініть існуючий шлях `/auth/github/callback`, щоб встановити `req.session.user_id = req.user.id`, і перенаправте до `/chat`.
|
||||
|
||||
Додайте `socket.io@~2.3.0` як залежність і запросіть/встановіть її на свій сервер, визначивши наступним чином, із `http` (постачається вбудованою з Nodejs):
|
||||
|
||||
```javascript
|
||||
const http = require('http').createServer(app);
|
||||
const io = require('socket.io')(http);
|
||||
```
|
||||
|
||||
Тепер, коли сервер *http* встановлений в *експрес застосунку*, чекайте відповіді від сервера *http*. Змініть рядок `app.listen` на `http.listen`.
|
||||
|
||||
Спершу необхідно прослухати нове з'єднання від клієнта. Ключове слово <dfn>on</dfn> робить лише це — прослуховує конкретну подію. Йому треба 2 аргумента: рядок, що містить заголовок події, що передається, і функція, якою проходять дані. У випадку нашого слухача зв'язку, скористаємось *socket*, щоб визначити дані в наступному аргументі. Сокет (socket) – це підключений індивідуальний клієнт.
|
||||
|
||||
Щоб прослухати підключення до вашого сервера, додайте до вашого підключення бази даних наступне:
|
||||
|
||||
```javascript
|
||||
io.on('connection', socket => {
|
||||
console.log('A user has connected');
|
||||
});
|
||||
```
|
||||
|
||||
Тепер, щоб клієнт під'єднався, вам треба просто додати до вашого `client.js`, що завантажується сторінкою після автентифікації, наступне:
|
||||
|
||||
```js
|
||||
/*global io*/
|
||||
let socket = io();
|
||||
```
|
||||
|
||||
Цей коментар замовчує помилку, яку ви б зазвичай бачили, оскільки 'io' не визначено в файлі. Ми вже додали надійну CDN до бібліотеки Socket.IO на сторінці chat.pug.
|
||||
|
||||
Тепер спробуйте завантажити свій додаток і автентифікуватись, і ви маєте побачити в консолі серверу напис 'A user has connected'!
|
||||
|
||||
**Примітка:**`io()` працює лише тоді коли підключений до сокета, який знаходиться на тому ж url/сервері. Щоб підключитись до зовнішнього сокета, який знаходиться в іншому місці, скористайтесь `io.connect('URL');`.
|
||||
|
||||
Підтвердіть свою сторінку, коли зрозумієте, що все працює коректно. Якщо ви натрапите на помилки, можете перевірити виконання проєкту до цього етапу [тут](https://gist.github.com/camperbot/aae41cf59debc1a4755c9a00ee3859d1).
|
||||
|
||||
# --hints--
|
||||
|
||||
`socket.io` має бути залежністю.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/package.json').then(
|
||||
(data) => {
|
||||
var packJson = JSON.parse(data);
|
||||
assert.property(
|
||||
packJson.dependencies,
|
||||
'socket.io',
|
||||
'Your project should list "socket.io" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви маєте правильно запросити й встановити `http` як `http`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/http.*=.*require.*('|")http\1/gi,
|
||||
'Your project should list "http" as a dependency'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви маєте правильно запросити й встановити `socket.io` як `io`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.*=.*require.*('|")socket.io\1.*http/gi,
|
||||
'You should correctly require and instantiate socket.io as io.'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Socket.IO має прослуховувати з'єднання.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/server.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/io.on.*('|")connection\1.*socket/gi,
|
||||
'io should listen for "connection" and socket should be the 2nd arguments variable'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Клієнт має бути підключеним до вашого сервера.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/public/client.js').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/socket.*=.*io/gi,
|
||||
'Your client should be connection to server with the connection defined as socket'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: 5895f70bf9fc0f352b528e64
|
||||
title: Використовуйте можливості шаблонного рушія
|
||||
challengeType: 2
|
||||
forumTopicId: 301567
|
||||
dashedName: use-a-template-engines-powers
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Одна з головних переваг використання шаблонного рушія – це можливість передавати змінні з сервера в файл шаблону перед його візуалізацією в HTML.
|
||||
|
||||
У вашому файлі Pug ви можете використовувати змінну, посилаючись на ім'я змінної як `#{variable_name}` в рядку з іншим текстом в елементі або використовуючи знак рівності в елементі без пробілу, наприклад, `p=variable_name`, що присвоює значення змінної тексту елемента p.
|
||||
|
||||
Ми рекомендуємо вивчити синтаксис і структуру Pug [тут](https://github.com/pugjs/pug) у README GitHub. Файл Pug націлений на використання прогалин і вкладок для зображення гніздових елементів і скорочення обсягу коду, необхідного для створення красивого сайту.
|
||||
|
||||
Коли йдеться про наш файл pug «index.pug», включений у ваш проєкт, ми використовували змінні *title* і *message*.
|
||||
|
||||
Щоб передати їх з нашого сервера, вам потрібно буде додати об'єкт як другий аргумент у ваш *res.render* зі змінними та їх значеннями. Наприклад, передайте цьому об'єкту установку змінних для індексного перегляду: `{title: 'Hello', message: 'Please login'}`
|
||||
|
||||
Це має мати наступний вигляд: `res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});` Тепер оновіть свою сторінку. Під час перегляду ви повинні побачити ці значення у потрібному місці, як зазначено у файлі index.pug!
|
||||
|
||||
Якщо вам вдалося, запустіть сторінку. Якщо ви зіткнулися з помилками, можете перевірити завершений до цього етапу проєкт [тут](https://gist.github.com/camperbot/4af125119ed36e6e6a8bb920db0c0871).
|
||||
|
||||
# --hints--
|
||||
|
||||
Файл Pug повинен правильно відображати змінні.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/').then(
|
||||
(data) => {
|
||||
assert.match(
|
||||
data,
|
||||
/pug-variable("|')>Please login/gi,
|
||||
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.statusText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: 587d824c367417b2b2512c4c
|
||||
title: Підтвердження глибокої рівності за допомогою .deepEqual та .notDeepEqual
|
||||
challengeType: 2
|
||||
forumTopicId: 301587
|
||||
dashedName: assert-deep-equality-with--deepequal-and--notdeepequal
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`deepEqual()` підтверджує, що два об'єкти дуже однакові.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#7` в наборі `Equality` змініть кожний `assert` на `assert.deepEqual` або `assert.notDeepEqual`, щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `deepEqual` або `notDeepEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'deepEqual',
|
||||
'The order of the keys does not matter'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `deepEqual` або `notDeepEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'notDeepEqual',
|
||||
'The position of elements within an array does matter'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,109 @@
|
||||
---
|
||||
id: 587d824c367417b2b2512c4d
|
||||
title: Порівняйте властивості двох елементів
|
||||
challengeType: 2
|
||||
forumTopicId: 301588
|
||||
dashedName: compare-the-properties-of-two-elements
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт будується на основі наступного стартового проєкту на [Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai), або клонується з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `test/1_unit-tests.js` під тестом з міткою `#8` у наборі `Comparisons` змініть кожне `assert` на `assert.isAbove` або `assert.isAtMost`, щоб тест міг бути успішно пройдений (слід оцінити як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=7').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для першого твердження - `isAbove` або `isAtMost`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=7').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'isAtMost',
|
||||
'5 is at most (<=) 5'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для другого твердження - `isAbove` або `isAtMost`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=7').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'isAbove', '1 is greater than 0');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні обрати правильний метод третього твердження - `isAbove` або `isAtMost`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=7').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'isAbove',
|
||||
'Math.PI = 3.14159265 is greater than 3'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для четвертого твердження - `isAbove` або `isAtMost`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=7').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[3].method,
|
||||
'isAtMost',
|
||||
'1 - Math.random() is > 0 and <= 1. It is atMost 1 !'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,75 @@
|
||||
---
|
||||
id: 587d824a367417b2b2512c46
|
||||
title: Дізнайтесь як працює твердження JavaScript
|
||||
challengeType: 2
|
||||
forumTopicId: 301589
|
||||
dashedName: learn-how-javascript-assertions-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Робота над цими завданнями передбачає написання коду одним із таких методів:
|
||||
|
||||
- Створіть копію [цього репозиторію](https://github.com/freeCodeCamp/boilerplate-mochachai/) та виконайте ці завдання локально.
|
||||
- Використовуйте [наш стартовий проєкт Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) для виконання цих завдань.
|
||||
- Для виконання проєкту скористуйтеся будь-яким конструктором сайтів на ваш розсуд. Впевніться, що ви зберегли усі файли з нашого GitHub репозиторію.
|
||||
|
||||
Завершивши роботу, переконайтеся, що робоча демоверсія вашого проєкту розміщена у відкритому доступі. Потім введіть URL-адресу проєкту у поле `Solution Link`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#1` в наборі `Basic Assertions` змініть кожне `assert`, або `assert.isNull`, або `assert.isNotNull` щоб пройти тест, який має оцінити `true`. Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти усі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=0').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `isNull` або`isNotNull`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=0').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'isNull', 'Null is null');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `isNull` або `isNotNull`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=0').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'isNotNull', '1 is not null');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,148 @@
|
||||
---
|
||||
id: 587d824f367417b2b2512c5a
|
||||
title: Запустіть функціональне тестування на відповідь API за допомогою Chai-HTTP III - PUT методу
|
||||
challengeType: 2
|
||||
forumTopicId: 301590
|
||||
dashedName: run-functional-tests-on-an-api-response-using-chai-http-iii---put-method
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі стартового проєкту на [ Replit ](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або клонується з [GitHub ](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
Коли ви протестуєте `PUT` запит, ви часто будете посилати дані разом з ним. Дані, які ви включаєте до вашого `PUT` запиту, називаються тілом запиту.
|
||||
|
||||
Щоб відправити `PUT` запит і об'єкт JSON до `'/travellers'`, ви можете використовувати плагіни `chai-http`, `put` і `send` методи:
|
||||
|
||||
```js
|
||||
chai
|
||||
.request(server)
|
||||
.put('/travellers')
|
||||
.send({
|
||||
"surname": [last name of a traveller of the past]
|
||||
})
|
||||
...
|
||||
```
|
||||
|
||||
І маршрут відповідає:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": [first name],
|
||||
"surname": [last name],
|
||||
"dates": [birth - death years]
|
||||
}
|
||||
```
|
||||
|
||||
Перегляньте код сервера для різних відповідей на кінцевій точці `'/travellers'`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/2_functional-tests.js` змініть `'Send {surname: "Colombo"}'`, перевірте (`// #3`) та використайте методи `put` та `send`, щоб перевірити кінцеву точку `'/travellers'`.
|
||||
|
||||
Надішліть наступний об'єкт JSON з вашим PUT-запитом:
|
||||
|
||||
```json
|
||||
{
|
||||
"surname": "Colombo"
|
||||
}
|
||||
```
|
||||
|
||||
Перевірте наступне у межах виклику `request.end`:
|
||||
|
||||
1. `status` має бути `200`
|
||||
2. `type` має бути `application/json`
|
||||
3. `body.name` має бути `Cristoforo`
|
||||
4. `body.surname` має бути `Colombo`
|
||||
|
||||
Дотримуйтесь порядку тверджень вище - ми покладаємося на нього. Також обов'язково видаліть `assert.fail()` після завершення.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти усі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні перевірити, чи значення `res.status` становить 200.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'equal');
|
||||
assert.equal(data.assertions[0].args[0], 'res.status');
|
||||
assert.equal(data.assertions[0].args[1], '200');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.type` на `'application/json'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'equal');
|
||||
assert.equal(data.assertions[1].args[0], 'res.type');
|
||||
assert.match(data.assertions[1].args[1], /('|")application\/json\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.body.name` на `'Cristoforo'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'equal');
|
||||
assert.equal(data.assertions[2].args[0], 'res.body.name');
|
||||
assert.match(data.assertions[2].args[1], /('|")Cristoforo\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.body.surname` на `'Colombo'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[3].method, 'equal');
|
||||
assert.equal(data.assertions[3].args[0], 'res.body.surname');
|
||||
assert.match(data.assertions[3].args[1], /('|")Colombo\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,126 @@
|
||||
---
|
||||
id: 587d824f367417b2b2512c5b
|
||||
title: Запуск функціональних тестів на відповідь API за допомогою методу Chai-HTTP IV - PUT
|
||||
challengeType: 2
|
||||
forumTopicId: 301591
|
||||
dashedName: run-functional-tests-on-an-api-response-using-chai-http-iv---put-method
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
Ця вправа схожа на попередню.
|
||||
|
||||
Тепер, коли ви знаєте, як перевірити запит `PUT`, ваша черга зробити це з нуля.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/2_functional-tests.js` змініть `'Send {surname: "da Verrazzano"}'`, перевірте (`// #4`) та використайте методи `put` та `send`, щоб перевірити кінцеву точку `'/travellers'`.
|
||||
|
||||
Надішліть наступний об'єкт JSON з вашим PUT-запитом:
|
||||
|
||||
```json
|
||||
{
|
||||
"surname": "da Verrazzano"
|
||||
}
|
||||
```
|
||||
|
||||
Перевірте наступне у межах виклику `request.end`:
|
||||
|
||||
1. `status` має бути `200`
|
||||
2. `type` має бути `application/json`
|
||||
3. `body.name` має бути `Giovanni`
|
||||
4. `body.surname` має бути `da Verrazzano`
|
||||
|
||||
Дотримуйтесь порядку тверджень вище – ми покладаємося на нього. Також обов'язково видаліть `assert.fail()` після завершення.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.status` на 200
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'equal');
|
||||
assert.equal(data.assertions[0].args[0], 'res.status');
|
||||
assert.equal(data.assertions[0].args[1], '200');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.type` на `'application/json'`
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'equal');
|
||||
assert.equal(data.assertions[1].args[0], 'res.type');
|
||||
assert.match(data.assertions[1].args[1], /('|")application\/json\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.body.name` на `'Giovanni'`
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'equal');
|
||||
assert.equal(data.assertions[2].args[0], 'res.body.name');
|
||||
assert.match(data.assertions[2].args[1], /('|")Giovanni\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.body.surname` на `'da Verrazzano'`
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[3].method, 'equal');
|
||||
assert.equal(data.assertions[3].args[0], 'res.body.surname');
|
||||
assert.match(data.assertions[3].args[1], /('|")da Verrazzano\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,75 @@
|
||||
---
|
||||
id: 587d824f367417b2b2512c59
|
||||
title: Запуск функціональних тестів на кінцевих точках API за допомогою Chai-HTTP II
|
||||
challengeType: 2
|
||||
forumTopicId: 301592
|
||||
dashedName: run-functional-tests-on-api-endpoints-using-chai-http-ii
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/2_functional-tests.js`, змініть тест `'Test GET /hello with your name'` (`// #2`) для перевірки відповідей `status` та `text` для проходження тесту.
|
||||
|
||||
Надішліть своє ім'я як запит URL, додавши до маршруту `?name=<your_name>`. Кінцева точка відповідає `'hello <your_name>'`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.status` == 200
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'equal');
|
||||
assert.equal(data.assertions[0].args[0], 'res.status');
|
||||
assert.equal(data.assertions[0].args[1], '200');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.text` == `'hello <your_name>'`
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'equal');
|
||||
assert.equal(data.assertions[1].args[0], 'res.text');
|
||||
assert.match(data.assertions[1].args[1], /hello [\w\d_-]/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,100 @@
|
||||
---
|
||||
id: 587d824e367417b2b2512c58
|
||||
title: Запуск функціональних тестів на кінцевих точках API за допомогою Chai-HTTP
|
||||
challengeType: 2
|
||||
forumTopicId: 301593
|
||||
dashedName: run-functional-tests-on-api-endpoints-using-chai-http
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
Mocha дозволяє вам тестувати асинхронні операції, такі як виклики в кінцевих точках API з плагіном `chai-http`.
|
||||
|
||||
Приклад тесту за допомогою `chai-http` для набору `'GET /hello?name=[name] => "hello [name]"'`:
|
||||
|
||||
```js
|
||||
suite('GET /hello?name=[name] => "hello [name]"', function () {
|
||||
test('?name=John', function (done) {
|
||||
chai
|
||||
.request(server)
|
||||
.get('/hello?name=John')
|
||||
.end(function (err, res) {
|
||||
assert.equal(res.status, 200, 'Response status should be 200');
|
||||
assert.equal(res.text, 'hello John', 'Response should be "hello John"');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Тест відправляє запит `GET` до сервера з ім’ям як рядок запиту URL (`?name=John`). У функії зворотного виклику методу `end` відповідь об'єкта (`res`) отримується і містить властивість `status`.
|
||||
|
||||
Перший `assert.equal` перевіряє, чи стан дорівнює `200`. Другий `assert.equal` перевіряє, що рядок відповіді (`res.text`) містить `"hello John"`.
|
||||
|
||||
Також зверніть увагу на параметр `done` у функції тесту зворотного виклику. Виклик без аргументу в кінці тесту є необхідним, щоб асинхронна операція була завершена.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/2_functional-tests.js`, змініть тест `'Test GET /hello with no name'` (`// #1`) для перевірки відповідей `status` та `text` для проходження тесту. Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
Не має бути запитів URL. Без імені запиту URL кінцева точка відповідає `hello Guest`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=0').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.status` == 200
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=0').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'equal');
|
||||
assert.equal(data.assertions[0].args[0], 'res.status');
|
||||
assert.equal(data.assertions[0].args[1], '200');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Перевірте значення `res.text` == `'hello Guest'`
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=0').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'equal');
|
||||
assert.equal(data.assertions[1].args[0], 'res.text');
|
||||
assert.match(data.assertions[1].args[1], /('|")hello Guest\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,115 @@
|
||||
---
|
||||
id: 5f8884f4c46685731aabfc41
|
||||
title: Запуск функціональних тестів за допомогою Headless Browser II
|
||||
challengeType: 2
|
||||
forumTopicId: 301594
|
||||
dashedName: run-functional-tests-using-a-headless-browser-ii
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/2_functional-tests.js` у тесті `'Submit the surname "Vespucci" in the HTML form'` (`// #5`) потрібно автоматизувати такі умови:
|
||||
|
||||
1. Впишіть у форму прізвище `Vespucci`
|
||||
2. Натисніть кнопку підтвердження
|
||||
|
||||
У межах кнопки зворотного виклику `pressButton`:
|
||||
|
||||
1. Підтвердьте, що статус – OK `200`
|
||||
2. Підтвердьте, що текст всередині елемента `span#name` – це `'Amerigo'`
|
||||
3. Підтвердьте, що текст всередині елемента `span#surname` – це `'Vespucci'`
|
||||
4. Підтвердьте, що елемент(и) `span#dates` існують і їхній підрахунок дорівнює `1`
|
||||
|
||||
Не забудьте видалити виклик `assert.fail()`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Вам слід підтвердити, що запит безголового браузера був успішним.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'browser.success');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте, що текст всередині елемента `span#name`> – це `'Amerigo'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'browser.text');
|
||||
assert.match(data.assertions[1].args[0], /('|")span#name\1/);
|
||||
assert.match(data.assertions[1].args[1], /('|")Amerigo\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте, що текст всередині елемента `span#surname` – це `'Vespucci'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'browser.text');
|
||||
assert.match(data.assertions[2].args[0], /('|")span#surname\1/);
|
||||
assert.match(data.assertions[2].args[1], /('|")Vespucci\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте, що елемент `span#dates` існує, а його підрахунок - 1.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=6').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[3].method, 'browser.elements');
|
||||
assert.match(data.assertions[3].args[0], /('|")span#dates\1/);
|
||||
assert.equal(data.assertions[3].args[1], 1);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,148 @@
|
||||
---
|
||||
id: 587d8250367417b2b2512c5d
|
||||
title: Запуск функціональних тестів за допомогою Headless Browser
|
||||
challengeType: 2
|
||||
forumTopicId: 301595
|
||||
dashedName: run-functional-tests-using-a-headless-browser
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
На сторінці знаходиться вхідна форма. Дані надсилаються до кінцевої точки `PUT /travellers` як запит AJAX.
|
||||
|
||||
Коли запит успішно виконано, код клієнта додає `<div>`, що містить інформацію у відповідь на DOM.
|
||||
|
||||
Ось приклад використання Zombie.js для взаємодії з формою:
|
||||
|
||||
```js
|
||||
test('Submit the surname "Polo" in the HTML form', function (done) {
|
||||
browser.fill('surname', 'Polo').then(() => {
|
||||
browser.pressButton('submit', () => {
|
||||
browser.assert.success();
|
||||
browser.assert.text('span#name', 'Marco');
|
||||
browser.assert.text('span#surname', 'Polo');
|
||||
browser.assert.elements('span#dates', 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Спершу метод `fill` об'єкта `browser` заповнює поле форми `surname` зі значенням `'Polo'`. `fill` повертає Promise, тоді `then` вимикається.
|
||||
|
||||
У межах зворотного виклику `then` метод `pressButton` об'єкта `browser` використовується для виклику слухача події форми `submit`. Метод `pressButton` є асинхронним.
|
||||
|
||||
Як тільки відповідь буде отримана від запиту AJAX, виникає кілька тверджень:
|
||||
|
||||
1. Статус відповіді `200`
|
||||
2. Текст усередині елемента `<span id='name'></span>` збігається з `'Marco'`
|
||||
3. Текст усередині елемента `<span id='surname'></span>` збігається з `'Polo'`
|
||||
4. Є елемент `1` `<span id='dates'></span>`.
|
||||
|
||||
Зрештою, запущено зворотний виклик `done`, який необхідний для асинхронного тесту.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/2_functional-tests.js` у тесті `'Submit the surname "Colombo" in the HTML form'` (`// #5`) потрібно автоматизувати такі умови:
|
||||
|
||||
1. Впишіть у форму прізвище `Colombo`
|
||||
2. Натисніть кнопку підтвердження
|
||||
|
||||
У межах кнопки зворотного виклику `pressButton`:
|
||||
|
||||
1. Підтвердьте, що статус – OK `200`
|
||||
2. Підтвердьте, що текст всередині елемента `span#name` – це `'Cristoforo'`
|
||||
3. Підтвердьте, що текст всередині елемента `span#surname` – це `'Colombo'`
|
||||
4. Підтвердьте, що елемент(и) `span#dates` існують і їхній підрахунок дорівнює `1`
|
||||
|
||||
Не забудьте видалити виклик `assert.fail()`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Вам слід підтвердити, що запит headless browser був успішним.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'browser.success');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте, що текст всередині елемента `span#name`> – це `'Cristoforo'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'browser.text');
|
||||
assert.match(data.assertions[1].args[0], /('|")span#name\1/);
|
||||
assert.match(data.assertions[1].args[1], /('|")Cristoforo\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте, що текст всередині елемента `span#surname` – це `'Colombo'`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'browser.text');
|
||||
assert.match(data.assertions[2].args[0], /('|")span#surname\1/);
|
||||
assert.match(data.assertions[2].args[1], /('|")Colombo\1/);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Підтвердьте, що елемент `span#dates` існує, а його підрахунок - 1.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[3].method, 'browser.elements');
|
||||
assert.match(data.assertions[3].args[0], /('|")span#dates\1/);
|
||||
assert.equal(data.assertions[3].args[1], 1);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,72 @@
|
||||
---
|
||||
id: 587d824f367417b2b2512c5c
|
||||
title: Імітувати дії з використанням Headless Browser
|
||||
challengeType: 2
|
||||
dashedName: simulate-actions-using-a-headless-browser
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт будується на основі наступного стартового проєкту на [Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai), або клонується з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
У наступних завданнях ви зможете імітувати взаємодію людини зі сторінкою за допомогою браузера без графічного інтерфейсу.
|
||||
|
||||
Headless browsers - це веб-браузери без GUI (графічного інтерфейсу). Вони можуть візуалізувати та інтерпретувати HTML, CSS та JavaScript так само і звичайний браузер, що робить їх надзвичайно корисними для тестування веб-сторінок.
|
||||
|
||||
Для вирішення наступних завдань ви будете використовувати Zombie.js - це легкий браузер без графічного інтерфейсу, який не покладається на встановлення додаткових бінарних файлів. Ця функція робить його придатним для використання в обмежених середовищах, таких як Replit. Але є ще багато інших потужніших опцій браузера без графічного інтерфейсу.
|
||||
|
||||
Mocha дозволяє вам запустити певний код перед виконанням будь-якого реального тесту. Це може бути корисним для таких дій, як додавання записів до бази даних, які будуть використовуватися в решті тестів.
|
||||
|
||||
У headless браузері перед запуском тестів вам потрібно **відвідати** сторінку, яку ви тестуватимете.
|
||||
|
||||
Хук `suiteSetup` виконується лише один раз, на початку тестового набору.
|
||||
|
||||
Існує кілька інших типів хуків, які можуть виконувати код перед кожним тестом, після кожного тесту або в кінці набору тестів. Для більш детальної інформації перегляньте документацію Mocha.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `test/2_functional-tests.js` одразу після оголошення `Browser` додайте URL-адресу свого проєкту до властивості `site` змінної:
|
||||
|
||||
```js
|
||||
Browser.site = 'https://boilerplate-mochachai.your-username.repl.co'; // Your URL here
|
||||
```
|
||||
|
||||
Потім на кореневому рівні `'Functional Tests with Zombie.js'`, створіть екземпляр нового об'єкта `Browser` з наступним кодом:
|
||||
|
||||
```js
|
||||
const browser = new Browser();
|
||||
```
|
||||
|
||||
І використовуйте хук `suiteSetup`, щоб направити `browser` до маршруту `/` за наступним кодом:
|
||||
|
||||
```js
|
||||
suiteSetup(function(done) {
|
||||
return browser.visit('/', done);
|
||||
});
|
||||
```
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти всі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=functional&n=4').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,101 @@
|
||||
---
|
||||
id: 587d824b367417b2b2512c49
|
||||
title: Тест на істинність
|
||||
challengeType: 2
|
||||
forumTopicId: 301596
|
||||
dashedName: test-for-truthiness
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`isTrue()` протестує логічне значення `true` та `isNotTrue()` передасть дані, коли отримає будь-що окрім логічного значення `true`.
|
||||
|
||||
```js
|
||||
assert.isTrue(true, 'This will pass with the boolean value true');
|
||||
assert.isTrue('true', 'This will NOT pass with the string value "true"');
|
||||
assert.isTrue(1, 'This will NOT pass with the number value 1');
|
||||
```
|
||||
|
||||
`isFalse()` та `isNotFalse()` також існують, і мають схожу поведінку до своїх true двійників, якщо тільки не шукають логічне значення `false`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У `tests/1_unit-tests.js` тесті з позначкою `#4` в наборі `Basic Assertions` змініть кожне `assert` на `assert.isTrue`, або `assert.isNotTrue`, щоб пройти тест (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `isTrue` або `isNotTrue`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'isTrue', 'True is true');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `isTrue` або `isNotTrue`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'isTrue',
|
||||
'Double negation of a truthy value is true'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження – `isTrue` або `isNotTrue`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=3').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'isNotTrue',
|
||||
'A truthy object is not true - neither is a false one'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: 587d824d367417b2b2512c53
|
||||
title: Перевірте, чи містить рядковий тип підрядок
|
||||
challengeType: 2
|
||||
forumTopicId: 301597
|
||||
dashedName: test-if-a-string-contains-a-substring
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`include()` та `notInclude()` також працює і для рядків! `include()` перевіряє, чи містить фактичний рядок очікуваний підрядок.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#14` в наборі `Strings` змініть кожний `assert` на `assert.include` або `assert.notInclude` щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=13').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `include` або `notInclude`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=13').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'include',
|
||||
"'Arrow' contains 'row'..."
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `include` або `notInclude`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=13').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'notInclude',
|
||||
"... a 'dart' doesn't contain a 'queue'"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: 587d824c367417b2b2512c4f
|
||||
title: Перевірте, чи знаходиться значення у межах конкретного діапазону
|
||||
challengeType: 2
|
||||
forumTopicId: 301598
|
||||
dashedName: test-if-a-value-falls-within-a-specific-range
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
```javascript
|
||||
.approximately(actual, expected, delta, [message])
|
||||
```
|
||||
|
||||
Перевіряє, щоб `actual` дорівнював `expected`, у межах +/- `delta` діапазону.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#10` в наборі `Comparisons` змініть кожний `assert` на `assert.approximately`, щоб полегшити проходження тесту (варто оцінювати як `true`).
|
||||
|
||||
Виберіть мінімальний діапазон (третій параметр) для того, щоб тест можна було проходити завжди. Він має бути менше ніж 1.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=9').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Оберіть правильний діапазон для першого твердження - `approximately(actual, expected, range)`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=9').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'approximately');
|
||||
assert.equal(
|
||||
data.assertions[0].args[2],
|
||||
0.5,
|
||||
"weirdNumbers(0.5) is in the range (0.5, 1.5]. It's within 1 +/- 0.5"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Оберіть правильний діапазон для другого твердження - `approximately(actual, expected, range)`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=9').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'approximately');
|
||||
assert.equal(
|
||||
data.assertions[1].args[2],
|
||||
0.8,
|
||||
"weirdNumbers(0.2) is in the range (0.2, 1.2]. It's within 1 +/- 0.8"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: 587d824d367417b2b2512c52
|
||||
title: Перевірте, чи є значення рядком
|
||||
challengeType: 2
|
||||
forumTopicId: 301599
|
||||
dashedName: test-if-a-value-is-a-string
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`isString` або `isNotString` стверджує, що фактичне значення - це рядок.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#13` в наборі `Strings` змініть кожний `assert` на `assert.isString` або `assert.isNotString` щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=12').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `isString` або `isNotString`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=12').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'isNotString',
|
||||
'A float number is not a string'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження - `isString` або `isNotString`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=12').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'isString',
|
||||
'environment vars are strings (or undefined)'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження – `isString` або `isNotString`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=12').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'isString', 'A JSON is a string');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,77 @@
|
||||
---
|
||||
id: 587d824d367417b2b2512c50
|
||||
title: Перевірте, чи є значення масивом
|
||||
challengeType: 2
|
||||
forumTopicId: 301600
|
||||
dashedName: test-if-a-value-is-an-array
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#11` в наборі `Arrays` змініть кожний `assert` на `assert.isArray` або `assert.isNotArray` щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Необхідно пройти усі тести.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=10').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження - `isArray` vs. `isNotArray`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=10').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'isArray',
|
||||
'String.prototype.split() returns an Array'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження - `isArray` vs. `isNotArray`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=10').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'isNotArray',
|
||||
'Array.prototype.indexOf() returns a number'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,133 @@
|
||||
---
|
||||
id: 587d824e367417b2b2512c56
|
||||
title: Перевірте, чи має значення конкретний тип структури даних
|
||||
challengeType: 2
|
||||
forumTopicId: 301601
|
||||
dashedName: test-if-a-value-is-of-a-specific-data-structure-type
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`#typeOf` стверджує, що тип значення - це заданий рядок, що визначено `Object.prototype.toString`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#17` в наборі `Objects` змініть кожний `assert` на `assert.typeOf` або `assert.notTypeOf` щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=16').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження – `typeOf` або `notTypeOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=16').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'typeOf',
|
||||
'myCar is typeOf Object'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження –`typeOf` або `notTypeOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=16').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'typeOf',
|
||||
'Car.model is a String'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження –`typeOf` або `notTypeOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=16').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'notTypeOf',
|
||||
'Plane.wings is a Number (not a String)'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для четвертого твердження –`typeOf` або `notTypeOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=16').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[3].method,
|
||||
'typeOf',
|
||||
'Plane.engines is an Array'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для п'ятого твердження –`typeOf` або `notTypeOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=16').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[4].method,
|
||||
'typeOf',
|
||||
'Car.wheels is a Number'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,95 @@
|
||||
---
|
||||
id: 587d824b367417b2b2512c47
|
||||
title: Перевірте, чи визначено змінну або функцію
|
||||
challengeType: 2
|
||||
forumTopicId: 301602
|
||||
dashedName: test-if-a-variable-or-function-is-defined
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі наступного початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#2` в наборі `Basic Assertions` змініть кожний `assert` на `assert.isDefined()` або `assert.isUndefined()` щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `isDefined` або `isUndefined`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'isDefined',
|
||||
'Null is not undefined'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `isDefined` або `isUndefined`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'isUndefined',
|
||||
'Undefined is undefined'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження `isDefined` або `isUndefined`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=1').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'isDefined',
|
||||
'A string is not undefined'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,77 @@
|
||||
---
|
||||
id: 587d824d367417b2b2512c51
|
||||
title: Перевірте, чи містить масив предмет
|
||||
challengeType: 2
|
||||
forumTopicId: 301603
|
||||
dashedName: test-if-an-array-contains-an-item
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту[ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#12` в наборі `Arrays` змініть кожний `assert` на `assert.include` або `assert.notInclude`, щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=11').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `include` або `notInclude`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=11').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'notInclude',
|
||||
"It's summer in july..."
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `include` або `notInclude`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=11').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'include',
|
||||
'JavaScript is a backend language !!'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: 587d824e367417b2b2512c55
|
||||
title: Перевірка об'єкта на властивість
|
||||
challengeType: 2
|
||||
forumTopicId: 301604
|
||||
dashedName: test-if-an-object-has-a-property
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`property` стверджує, що певний об'єкт має задану властивість.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#16` у наборі `Objects` змініть кожний `assert` на `assert.property` або `assert.notProperty`, щоб забезпечити проходження тесту. (Має мати значення `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=15').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження –`property` у порівнянні з `notProperty`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=15').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'notProperty',
|
||||
'A car has not wings'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження –`property` у порівнянні з `notProperty`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=15').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'property',
|
||||
'planes have engines'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження –`property` у порівнянні з `notProperty`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=15').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'property', 'Cars have wheels');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,115 @@
|
||||
---
|
||||
id: 587d824e367417b2b2512c57
|
||||
title: Перевірка об'єкта на екземпляр конструктора
|
||||
challengeType: 2
|
||||
forumTopicId: 301605
|
||||
dashedName: test-if-an-object-is-an-instance-of-a-constructor
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai), або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`#instanceOf` стверджує, що об'єкт є екземпляром конструктора.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#18` в наборі `Objects` змініть кожний `assert` на `assert.instanceOf` або `assert.notInstanceOf`, щоб забезпечити проходження тесту (має мати значення `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=17').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження – `instanceOf` або `notInstanceOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=17').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'notInstanceOf',
|
||||
'myCar is not an instance of Plane'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження – `instanceOf` або `notInstanceOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=17').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'instanceOf',
|
||||
'airlinePlane is an instance of Plane'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження – `instanceOf` або `notInstanceOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=17').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'instanceOf',
|
||||
'everything is an Object in JavaScript...'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для четвертого твердження – `instanceOf` або `notInstanceOf`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=17').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[3].method,
|
||||
'notInstanceOf',
|
||||
'myCar.wheels is not an instance of String'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,109 @@
|
||||
---
|
||||
id: 587d824c367417b2b2512c4e
|
||||
title: Перевірка, чи є одне значення нижчим або принаймні таке саме за величиною, як інше
|
||||
challengeType: 2
|
||||
forumTopicId: 301606
|
||||
dashedName: test-if-one-value-is-below-or-at-least-as-large-as-another
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створюється на основі початкового проєкту на [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або копіюється з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `test/1_unit-tests.js` під тестом з міткою `#9` у наборі `Comparisons` змініть кожне `assert` на `assert.isBelow` або `assert.isAtLeast`, щоб тест міг бути успішно пройдений (слід оцінити як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=8').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для першого твердження – `isBelow` або `isAtLeast`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=8').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'isAtLeast',
|
||||
'5 is at least (>=) 5'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для другого твердження – `isBelow` або `isAtLeast`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=8').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'isAtLeast',
|
||||
'2 * Math.random() is at least 0'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для третього твердження – `isBelow` або `isAtLeast`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=8').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'isBelow', '1 is smaller than 2');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Ви повинні вибрати правильний метод для четвертого твердження – `isBelow` або `isAtLeast`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=8').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[3].method,
|
||||
'isBelow',
|
||||
'2/3 (0.6666) is smaller than 1'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: 587d824b367417b2b2512c48
|
||||
title: Використання Assert.isOK і Assert.isNotOK
|
||||
challengeType: 2
|
||||
forumTopicId: 301607
|
||||
dashedName: use-assert-isok-and-assert-isnotok
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт будується на основі такого початкового проєкту на [ Replit ](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або клонується з [ GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`isOk()` перевіряє правдиве значення, а `isNotOk()` перевіряє хибне значення.
|
||||
|
||||
Щоб дізнатися більше про правдиві й хибні значення, перейдіть на завдання [Хибний вибивало](https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/basic-algorithm-scripting/falsy-bouncer).
|
||||
|
||||
# --instructions--
|
||||
|
||||
У `tests/1_unit-tests.js` тесті з позначкою `#3` у наборі `Basic Assertions` змініть кожне `assert` на `assert.isOk()` або `assert.isNotOk()`, щоб пройти тест (має мати значення `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження – `isOk` у порівнянні з `isNotOk`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[0].method, 'isNotOk', 'Null is falsy');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження – `isOk` у порівнянні з `isNotOk`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'isOk', 'A string is truthy');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження – `isOk` у порівнянні з `isNotOk`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=2').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[2].method, 'isOk', 'true is truthy');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: 587d824d367417b2b2512c54
|
||||
title: Використання звичайних виразів для перевірки рядка
|
||||
challengeType: 2
|
||||
forumTopicId: 301608
|
||||
dashedName: use-regular-expressions-to-test-a-string
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`match()` стверджує, що фактичне значення відповідає звичайному виразу другого аргументу.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#15` у наборі `Strings` змініть кожний `assert` на `assert.match` або `assert.notMatch`, щоб забезпечити проходження тесту. (Має мати значення `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=14').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження – `match` у порівнянні з `notMatch`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=14').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'match',
|
||||
"'# name:John Doe, age:35' matches the regex"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження – `match` у порівнянні з `notMatch`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=14').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'notMatch',
|
||||
"'# name:Paul Smith III, age:twenty-four' does not match the regex (the age must be numeric)"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,111 @@
|
||||
---
|
||||
id: 587d824b367417b2b2512c4a
|
||||
title: Використання подвійних рівностей для підтвердження рівності
|
||||
challengeType: 2
|
||||
forumTopicId: 301609
|
||||
dashedName: use-the-double-equals-to-assert-equality
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`equal()` порівнює об'єкти за допомогою `==`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#5` в наборі `Equality` змініть кожний `assert` на `assert.equal` або `assert.notEqual`, щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=4').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `equal` або `notEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=4').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'equal',
|
||||
'Numbers are coerced into strings with == '
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `equal` або `notEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=4').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[1].method,
|
||||
'notEqual',
|
||||
' == compares object references'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження `equal` або `notEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=4').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'equal',
|
||||
"6 * '2' is 12 ! It should be equal to '12'"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для четвертого твердження `equal` або `notEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=4').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[3].method, 'notEqual', "6 + '2' is '62'...");
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,111 @@
|
||||
---
|
||||
id: 587d824b367417b2b2512c4b
|
||||
title: Використання потрійних рівностей для підтвердження суворої рівності
|
||||
challengeType: 2
|
||||
forumTopicId: 301610
|
||||
dashedName: use-the-triple-equals-to-assert-strict-equality
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Нагадуємо, що цей проєкт створено на основі наступного стартового проєкту [ Replit](https://replit.com/github/freeCodeCamp/boilerplate-mochachai) або кальковано з [GitHub](https://github.com/freeCodeCamp/boilerplate-mochachai/).
|
||||
|
||||
`strictEqual()` порівнює об'єкти за допомогою `===`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
У межах `tests/1_unit-tests.js` під тестом з міткою `#6` в наборі `Equality` змініть кожний `assert` на `assert.strictEqual` або `assert.notStrictEqual`, щоб полегшити проходження тесту (варто оцінювати як `true`). Не змінюйте аргументи, передані до тверджень.
|
||||
|
||||
# --hints--
|
||||
|
||||
Всі тести повинні бути успішно пройдені.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.state, 'passed');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для першого твердження `strictEqual` або `notStrictEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[0].method,
|
||||
'notStrictEqual',
|
||||
'with strictEqual the type must match'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для другого твердження `strictEqual` або `notStrictEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(data.assertions[1].method, 'strictEqual', '3*2 = 6...');
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для третього твердження `strictEqual` або `notStrictEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[2].method,
|
||||
'strictEqual',
|
||||
"6 * '2' is 12. Types match !"
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Потрібно обрати правильний метод для четвертого твердження `strictEqual` або `notStrictEqual`.
|
||||
|
||||
```js
|
||||
(getUserInput) =>
|
||||
$.get(getUserInput('url') + '/_api/get-tests?type=unit&n=5').then(
|
||||
(data) => {
|
||||
assert.equal(
|
||||
data.assertions[3].method,
|
||||
'notStrictEqual',
|
||||
'Even if they have the same elements, the Arrays are notStrictEqual'
|
||||
);
|
||||
},
|
||||
(xhr) => {
|
||||
throw new Error(xhr.responseText);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,337 @@
|
||||
---
|
||||
id: 5e601c0d5ac9d0ecd8b94afe
|
||||
title: Перекладач американської та британської англійської мови
|
||||
challengeType: 4
|
||||
forumTopicId: 462358
|
||||
dashedName: american-british-translator
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Створіть повний пакет додатку JavaScript, який функціонально схожий до <https://american-british-translator.freecodecamp.rocks/>. Робота над цим проєктом включатиме написання коду, використовуючи один з таких методів:
|
||||
|
||||
- Клонувати [цей репозиторій GitHub ](https://github.com/freeCodeCamp/boilerplate-project-american-british-english-translator/) і частково завершити ваш проєкт.
|
||||
- Використати [наш стартовий проєкт Replit](https://replit.com/github/freeCodeCamp/boilerplate-project-american-british-english-translator), щоб завершити свій проєкт.
|
||||
- Для завершення проєкту, використайте вибраний вами розробник сайту. Переконайтеся, що зберегли усі файли з нашого репозиторію GitHub.
|
||||
|
||||
Коли ви завершили, переконайтеся, що ця демоверсія вашого проєкту розміщена у відкритому доступі. Потім введіть URL-адресу у поле `Solution Link`. При необхідності, також введіть посилання на джерело коду вашого проєкту у полі `GitHub Link`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
- Уся логіка може перейти до `/components/translator.js`
|
||||
- Завершіть маршрут `/api/translate` в `/routes/api.js`
|
||||
- Створіть усі сегменти/функціональні тести в `tests/1_unit-tests.js` та `tests/2_functional-tests.js`
|
||||
- Перегляньте файли JavaScript у `/components` для різного правопису і термінів вашого застосунку, які він повинен перекладати
|
||||
- Щоб розпочати тести на Replit, налаштуйте `NODE_ENV` на файл `test` без лапок в `.env`
|
||||
- Щоб розпочати тести в консолі, використайте команду `npm run test`. Щоб відкрити консоль Replit, натисніть сполучення клавіш Ctrl+Shift+P (Cmd, якщо з Mac) та наберіть "open shell"
|
||||
|
||||
Напишіть наступні тести в `tests/1_unit-tests.js`:
|
||||
|
||||
- Перекладіть британською англійською `Mangoes are my favorite fruit.`
|
||||
- Перекладіть британською англійською `I ate yogurt for breakfast.`
|
||||
- Перекладіть британською англійською `We had a party at my friend's condo.`
|
||||
- Перекладіть британською англійською `Can you toss this in the trashcan for me?`
|
||||
- Перекладіть британською англійською `The parking lot was full.`
|
||||
- Перекладіть британською англійською `Like a high tech Rube Goldberg machine.`
|
||||
- Перекладіть британською англійською `To play hooky means to skip class or work.`
|
||||
- Перекладіть британською англійською `No Mr. Bond, I expect you to die.`
|
||||
- Перекладіть британською англійською `Dr. Grosh will see you now.`
|
||||
- Перекладіть британською англійською `Lunch is at 12:15 today.`
|
||||
- Перекладіть американською англійською `We watched the footie match for a while.`
|
||||
- Перекладіть американською англійською `Paracetamol takes up to an hour to work.`
|
||||
- Перекладіть американською англійською `First, caramelise the onions.`
|
||||
- Перекладіть американською англійською `I spent the bank holiday at the funfair.`
|
||||
- Перекладіть американською англійською `I had a bicky then went to the chippy.`
|
||||
- Перекладіть американською англійською `I've just got bits and bobs in my bum bag.`
|
||||
- Перекладіть американською англійською `The car boot sale at Boxted Airfield was called off.`
|
||||
- Перекладіть американською англійською `Have you met Mrs Kalyani?`
|
||||
- Перекладіть американською англійською `Prof Joyner of King's College, London.`
|
||||
- Перекладіть американською англійською `Tea time is usually around 4 or 4.30.`
|
||||
- Виділіть переклад в `Mangoes are my favorite fruit.`
|
||||
- Виділіть переклад в `I ate yogurt for breakfast.`
|
||||
- Виділіть переклад в `We watched the footie match for a while.`
|
||||
- Виділіть переклад в `Paracetamol takes up to an hour to work.`
|
||||
|
||||
Напишіть наступні тести в `tests/2_functional-tests.js`:
|
||||
|
||||
- Переклад з текстом та полями локалізації: запит POST на `/api/translate`
|
||||
- Переклад тексту з недопустимими полями локалізації: запит POST на `/api/translate`
|
||||
- Переклад з пропущеним текстовим полем: запит POST на `/api/translate`
|
||||
- Переклад з пропущеним полем локалізації: запит POST на `/api/translate`
|
||||
- Переклад з порожнім текстом: запит POST на `/api/translate`
|
||||
- Переклад тексту, який не потребує перекладу: запит POST на `/api/translate`
|
||||
|
||||
# --hints--
|
||||
|
||||
Я можу надати свій власний проєкт, не приклад URL-адреси.
|
||||
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(
|
||||
!/.*\/american-british-translator\.freecodecamp\.rocks/.test(
|
||||
getUserInput('url')
|
||||
)
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете `POST` на `/api/translate`, з корпусом, що містить `text` з текстом для перекладу та `locale` з `american-to-british` чи `british-to-american`. Зворотний об'єкт повинен містити поданий `text` та `translation` з перекладеним текстом.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const text = 'Mangoes are my favorite fruit.';
|
||||
const locale = 'american-to-british';
|
||||
const output = {
|
||||
text: 'Mangoes are my favorite fruit.',
|
||||
translation:
|
||||
'Mangoes are my <span class="highlight">favourite</span> fruit.'
|
||||
};
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'text');
|
||||
assert.property(parsed, 'translation');
|
||||
assert.deepEqual(parsed, output);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Маршрут `/api/translate` повинен опрацьовувати час, написаний американською та британською англійською. Наприклад, десять тридцять написано як "10.30" британською англійською та "10:30" американською англійською. Елемент `span` повинен містити весь рядок часу, наприклад `<span class="highlight">10:30</span>`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const text = 'Lunch is at 12:15 today.';
|
||||
const locale = 'american-to-british';
|
||||
const output = {
|
||||
text: text,
|
||||
translation: 'Lunch is at <span class="highlight">12.15</span> today.'
|
||||
};
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'text');
|
||||
assert.property(parsed, 'translation');
|
||||
assert.deepEqual(parsed, output);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Маршрут `/api/translate` повинен також регулювати те як заголовки/ назви посад/титулів скорочені американською та британською англійською. Наприклад, абревіатура Doctor Wright скорочується на "Dr Wright" у британській англійській і "Dr. Wright" в американській англійській. Дивіться `/components/american-to-british-titles.js` для різних заголовків, які має опрацювати ваш додаток.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const text = 'Dr. Grosh will see you now.';
|
||||
const locale = 'american-to-british';
|
||||
const output = {
|
||||
text: text,
|
||||
translation: '<span class="highlight">Dr</span> Grosh will see you now.'
|
||||
};
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'text');
|
||||
assert.property(parsed, 'translation');
|
||||
assert.deepEqual(parsed, output);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Перенести будь-який перекладений правопис чи термін з тегів `<span class="highlight">...</span>` так, щоб вони стали зеленими.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const text = 'Mangoes are my favorite fruit.';
|
||||
const locale = 'american-to-british';
|
||||
const output = {
|
||||
text: 'Mangoes are my favorite fruit.',
|
||||
translation:
|
||||
'Mangoes are my <span class="highlight">favourite</span> fruit.'
|
||||
};
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'text');
|
||||
assert.property(parsed, 'translation');
|
||||
assert.deepEqual(parsed, output);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо одне чи більше необхідних полів відсутні, поверніться `{ error: 'Required field(s) missing' }`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const locale = 'american-to-british';
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, 'Required field(s) missing');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо `text` порожній, поверніться `{ error: 'No text to translate' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const locale = 'american-to-british';
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: '', locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, 'No text to translate');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо `locale` не збігається з однією з двох спеціальних мов, поверніться `{ error: 'Invalid value for locale field' }`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const text = "Ceci n'est pas une pipe";
|
||||
const locale = 'french-to-american';
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, 'Invalid value for locale field');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо `text` не потребує перекладу, поверніться `"Everything looks good to me!"` для значення `translation`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const locale = 'british-to-american';
|
||||
const output = {
|
||||
text: 'SaintPeter and nhcarrigan give their regards!',
|
||||
translation: 'Everything looks good to me!'
|
||||
};
|
||||
let data = await fetch(getUserInput('url') + '/api/translate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: output.text, locale })
|
||||
});
|
||||
let parsed = await data.json();
|
||||
assert.isObject(parsed);
|
||||
assert.isObject(parsed);
|
||||
assert.property(parsed, 'text');
|
||||
assert.property(parsed, 'translation');
|
||||
assert.deepEqual(parsed, output);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 24 модульних тестів завершено та успішно пройдено. Дивіться `/tests/1_unit-tests.js` для очікуваної поведінки об'єкту, для якої вам слід написати тести.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
const unitTests = getTests.filter((test) => {
|
||||
return !!test.context.match(/Unit Tests/gi);
|
||||
});
|
||||
assert.isAtLeast(unitTests.length, 24, 'At least 24 tests passed');
|
||||
unitTests.forEach((test) => {
|
||||
assert.equal(test.state, 'passed', 'Tests in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 6 функціональних тестів завершено та успішно пройдено. Дивіться `/tests/2_functional-tests.js` для функціональності, для якої вам слід написати тести.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
const functTests = getTests.filter((test) => {
|
||||
return !!test.context.match(/Functional Tests/gi);
|
||||
});
|
||||
assert.isAtLeast(functTests.length, 6, 'At least 6 tests passed');
|
||||
functTests.forEach((test) => {
|
||||
assert.equal(test.state, 'passed', 'Tests in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,369 @@
|
||||
---
|
||||
id: 587d8249367417b2b2512c42
|
||||
title: Система відстеження помилок
|
||||
challengeType: 4
|
||||
forumTopicId: 301569
|
||||
dashedName: issue-tracker
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Створіть повний пакет додатку JavaScript, який функціонально схожий до: <https://issue-tracker.freecodecamp.rocks/>. Робота над цим проєктом передбачає написання коду з використанням одного з наступних метод:
|
||||
|
||||
- Клонуйте цей [репозиторій GitHub ](https://github.com/freeCodeCamp/boilerplate-project-issuetracker/) і локально завершіть ваш проєкт.
|
||||
- Використати [наш стартовий проєкт Replit](https://replit.com/github/freeCodeCamp/boilerplate-project-issuetracker), щоб завершити свій проєкт.
|
||||
- Для завершення проєкту, використайте вибраний вами розробник сайту. Переконайтеся, що зберегли усі файли з нашого репозиторію GitHub.
|
||||
|
||||
Коли ви завершили, переконайтеся, що ця демоверсія вашого проєкту розміщена у відкритому доступі. Потім введіть URL-адресу в поле `Solution Link`. При необхідності, також введіть посилання на джерело коду вашого проєкту у полі`GitHub Link`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
- Завершіть необхідні маршрути в `/routes/api.js`
|
||||
- Створіть усі функціональні тести в `tests/2_functional-tests.js`
|
||||
- Скопіюйте файл `sample.env` до `.env` та встановіть відповідні змінні
|
||||
- Щоб розпочати тест розкоментуйте `NODE_ENV=test` у вашому файлі `.env`
|
||||
- Щоб розпочати тести на консолі, використайте команду `npm run test`. Щоб відкрити консоль Replit, натисніть сполучення клавіш Ctrl+Shift+P (Cmd, якщо на Mac) та наберіть "open shell"
|
||||
|
||||
Напишіть наступні тести в `tests/2_functional-tests.js`:
|
||||
|
||||
- Створіть запитання в кожному полі: запит POST на `/api/issues/{project}`
|
||||
- Створіть запитання тільки в необхідних полях: запит POST на `/api/issues/{project}`
|
||||
- Створіть запитання в пропущених необхідних полях: запит POST на `/api/issues/{project}`
|
||||
- Подивіться запитання проєкту: запит GET на `/api/issues/{project}`
|
||||
- Подивіться запитання проєкту з одним фільтром: запит GET на `/api/issues/{project}`
|
||||
- Подивіться запитання проєкту з декількома фільтрами: запит GET на `/api/issues/{project}`
|
||||
- Оновіть одне поле на запитанні: запит PUT на `/api/issues/{project}`
|
||||
- Оновіть декілька полів на запитанні: запит PUT на `/api/issues/{project}`
|
||||
- Оновіть запитання з пропущеним `_id`: запит PUT на `/api/issues/{project}`
|
||||
- Оновіть запитання з полями для оновлення: запит PUT на `/api/issues/{project}`
|
||||
- Оновіть запитання з недопустимим `_id`: запит PUT на `/api/issues/{project}`
|
||||
- Видаліть запитання: запит DELETE на `/api/issues/{project}`
|
||||
- Видаліть запитання з недопустимим `_id`: запит DELETE на `/api/issues/{project}`
|
||||
- Видаліть запитання з пропущеним `_id`: запит DELETE на `/api/issues/{project}`
|
||||
|
||||
# --hints--
|
||||
|
||||
Ви можете застосувати свій власний проєкт, а не URL-посилання прикладу.
|
||||
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(!/.*\/issue-tracker\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит `POST` на `/api/issues/{projectname}` з формою даних, яка містить необхідні поля `issue_title`, `issue_text`, `created_by` і при необхідності `assigned_to` та `status_text`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let test_data = {
|
||||
issue_title: 'Faux Issue Title',
|
||||
issue_text: 'Functional Test - Required Fields Only',
|
||||
created_by: 'fCC'
|
||||
};
|
||||
const data = await $.post(
|
||||
getUserInput('url') + '/api/issues/fcc-project',
|
||||
test_data
|
||||
);
|
||||
assert.isObject(data);
|
||||
assert.nestedInclude(data, test_data);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Запит `POST` на `/api/issues/{projectname}` повертає створений об'єкт і повинен включати усі подані поля. Виключені поля будуть повертатися як порожні рядки. Додатково, включіть `created_on` (дата/час), `updated_on` (дата/час), `open` (boolean, `true` для відкритого - значення за змовчуванням, `false` для закритого) та `_id`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let test_data = {
|
||||
issue_title: 'Faux Issue Title 2',
|
||||
issue_text: 'Functional Test - Every field filled in',
|
||||
created_by: 'fCC',
|
||||
assigned_to: 'Chai and Mocha'
|
||||
};
|
||||
const data = await $.post(
|
||||
getUserInput('url') + '/api/issues/fcc-project',
|
||||
test_data
|
||||
);
|
||||
assert.isObject(data);
|
||||
assert.nestedInclude(data, test_data);
|
||||
assert.property(data, 'created_on');
|
||||
assert.isNumber(Date.parse(data.created_on));
|
||||
assert.property(data, 'updated_on');
|
||||
assert.isNumber(Date.parse(data.updated_on));
|
||||
assert.property(data, 'open');
|
||||
assert.isBoolean(data.open);
|
||||
assert.isTrue(data.open);
|
||||
assert.property(data, '_id');
|
||||
assert.isNotEmpty(data._id);
|
||||
assert.property(data, 'status_text');
|
||||
assert.isEmpty(data.status_text);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо ви відправите запит `POST` на `/api/issues/{projectname}` без необхідних полів, то буде помилка `{ error: 'required field(s) missing' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let test_data = { created_by: 'fCC' };
|
||||
const data = await $.post(getUserInput('url') + '/api/issues/fcc-project', {
|
||||
created_by: 'fCC'
|
||||
});
|
||||
assert.isObject(data);
|
||||
assert.property(data, 'error');
|
||||
assert.equal(data.error, 'required field(s) missing');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит `GET` на `/api/issues/{projectname}` для масиву усіх запитань для конкретного `projectname` з усіма цими полями для кожного запитання.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let test_data = { issue_text: 'Get Issues Test', created_by: 'fCC' };
|
||||
const url =
|
||||
getUserInput('url') +
|
||||
'/api/issues/get_issues_test_' +
|
||||
Date.now().toString().substring(7);
|
||||
const data1 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { issue_title: 'Faux Issue 1' })
|
||||
);
|
||||
assert.isObject(data1);
|
||||
const data2 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { issue_title: 'Faux Issue 2' })
|
||||
);
|
||||
assert.isObject(data2);
|
||||
const data3 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { issue_title: 'Faux Issue 3' })
|
||||
);
|
||||
assert.isObject(data3);
|
||||
const getIssues = await $.get(url);
|
||||
assert.isArray(getIssues);
|
||||
assert.lengthOf(getIssues, 3);
|
||||
let re = new RegExp('Faux Issue \\d');
|
||||
getIssues.forEach((issue) => {
|
||||
assert.property(issue, 'issue_title');
|
||||
assert.match(issue.issue_title, re);
|
||||
assert.property(issue, 'issue_text');
|
||||
assert.property(issue, 'created_by');
|
||||
assert.property(issue, 'assigned_to');
|
||||
assert.property(issue, 'status_text');
|
||||
assert.property(issue, 'open');
|
||||
assert.property(issue, 'created_on');
|
||||
assert.property(issue, 'updated_on');
|
||||
assert.property(issue, '_id');
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит `GET` на `/api/issues/{projectname}` та фільтрувати запит, також передавши будь-яке поле і значення як запит URL (наприклад, `/api/issues/{project}?open=false`). Ви також можете одночасно передати одне чи декілька полів/значень.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let test_data = {
|
||||
issue_title: 'To be Filtered',
|
||||
issue_text: 'Filter Issues Test'
|
||||
};
|
||||
const url =
|
||||
getUserInput('url') +
|
||||
'/api/issues/get_issues_test_' +
|
||||
Date.now().toString().substring(7);
|
||||
const data1 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { created_by: 'Alice', assigned_to: 'Bob' })
|
||||
);
|
||||
const data2 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { created_by: 'Alice', assigned_to: 'Bob' })
|
||||
);
|
||||
const data3 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { created_by: 'Alice', assigned_to: 'Eric' })
|
||||
);
|
||||
const data4 = await $.post(
|
||||
url,
|
||||
Object.assign(test_data, { created_by: 'Carol', assigned_to: 'Eric' })
|
||||
);
|
||||
const getSingle = await $.get(url + '?created_by=Alice');
|
||||
assert.isArray(getSingle);
|
||||
assert.lengthOf(getSingle, 3);
|
||||
const getMultiple = await $.get(url + '?created_by=Alice&assigned_to=Bob');
|
||||
assert.isArray(getMultiple);
|
||||
assert.lengthOf(getMultiple, 2);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит `PUT` на `/api/issues/{projectname}` з `_id` та з одним чи більше полем для оновлення. Якщо все зроблено успішно, поле `updated_on` повинне оновитися і повернутися у вигляді `{ result: 'successfully updated', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let initialData = {
|
||||
issue_title: 'Issue to be Updated',
|
||||
issue_text: 'Functional Test - Put target',
|
||||
created_by: 'fCC'
|
||||
};
|
||||
const url = getUserInput('url') + '/api/issues/fcc-project';
|
||||
const itemToUpdate = await $.post(url, initialData);
|
||||
const updateSucccess = await $.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: { _id: itemToUpdate._id, issue_text: 'New Issue Text' }
|
||||
});
|
||||
assert.isObject(updateSucccess);
|
||||
assert.deepEqual(updateSucccess, {
|
||||
result: 'successfully updated',
|
||||
_id: itemToUpdate._id
|
||||
});
|
||||
const getUpdatedId = await $.get(url + '?_id=' + itemToUpdate._id);
|
||||
assert.isArray(getUpdatedId);
|
||||
assert.isObject(getUpdatedId[0]);
|
||||
assert.isAbove(
|
||||
Date.parse(getUpdatedId[0].updated_on),
|
||||
Date.parse(getUpdatedId[0].created_on)
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Коли запит `PUT` надіслати на `/api/issues/{projectname}` не включаючи `_id`, зворотне значення `{ error: 'missing _id' }`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const url = getUserInput('url') + '/api/issues/fcc-project';
|
||||
const badUpdate = await $.ajax({ url: url, type: 'PUT' });
|
||||
assert.isObject(badUpdate);
|
||||
assert.property(badUpdate, 'error');
|
||||
assert.equal(badUpdate.error, 'missing _id');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Коли запит `PUT` надіслати на `/api/issues/{projectname}` не включаючи оновлені поля, зворотне значення `{ error: 'no update field(s) sent', '_id': _id }`. При іншій помилці, зворотне значення буде `{ error: 'could not update', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const url = getUserInput('url') + '/api/issues/fcc-project';
|
||||
const badUpdate = await $.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: { _id: '5f665eb46e296f6b9b6a504d' }
|
||||
});
|
||||
assert.deepEqual(badUpdate, {
|
||||
error: 'no update field(s) sent',
|
||||
_id: '5f665eb46e296f6b9b6a504d'
|
||||
});
|
||||
const badIdUpdate = await $.ajax({
|
||||
url: url,
|
||||
type: 'PUT',
|
||||
data: { _id: '5f665eb46e296f6b9b6a504d', issue_text: 'New Issue Text' }
|
||||
});
|
||||
assert.deepEqual(badIdUpdate, {
|
||||
error: 'could not update',
|
||||
_id: '5f665eb46e296f6b9b6a504d'
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит `DELETE` на `/api/issues/{projectname}` з `_id`, щоб видалити помилку. Якщо не надіслано жодного `_id`, зворотне значення буде `{ error: 'missing _id' }`. Якщо успішно, зворотне значення - `{ result: 'successfully deleted', '_id': _id }`. Якщо невдало, зворотне значення - `{ error: 'could not delete', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let initialData = {
|
||||
issue_title: 'Issue to be Deleted',
|
||||
issue_text: 'Functional Test - Delete target',
|
||||
created_by: 'fCC'
|
||||
};
|
||||
const url = getUserInput('url') + '/api/issues/fcc-project';
|
||||
const itemToDelete = await $.post(url, initialData);
|
||||
assert.isObject(itemToDelete);
|
||||
const deleteSuccess = await $.ajax({
|
||||
url: url,
|
||||
type: 'DELETE',
|
||||
data: { _id: itemToDelete._id }
|
||||
});
|
||||
assert.isObject(deleteSuccess);
|
||||
assert.deepEqual(deleteSuccess, {
|
||||
result: 'successfully deleted',
|
||||
_id: itemToDelete._id
|
||||
});
|
||||
const noId = await $.ajax({ url: url, type: 'DELETE' });
|
||||
assert.isObject(noId);
|
||||
assert.deepEqual(noId, { error: 'missing _id' });
|
||||
const badIdDelete = await $.ajax({
|
||||
url: url,
|
||||
type: 'DELETE',
|
||||
data: { _id: '5f665eb46e296f6b9b6a504d', issue_text: 'New Issue Text' }
|
||||
});
|
||||
assert.isObject(badIdDelete);
|
||||
assert.deepEqual(badIdDelete, {
|
||||
error: 'could not delete',
|
||||
_id: '5f665eb46e296f6b9b6a504d'
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 14 функціональних тестів завершено та успішно пройдено.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
assert.isAtLeast(getTests.length, 14, 'At least 14 tests passed');
|
||||
getTests.forEach((test) => {
|
||||
assert.equal(test.state, 'passed', 'Test in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,318 @@
|
||||
---
|
||||
id: 587d8249367417b2b2512c41
|
||||
title: Метрично-імперський конвертер
|
||||
challengeType: 4
|
||||
forumTopicId: 301570
|
||||
dashedName: metric-imperial-converter
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Використовуючи JavaScript, створіть повноцінний додаток, який функціонально буде подібним до цього: <https://metric-imperial-converter.freecodecamp.rocks/>. Робота над цим проєктом включатиме ваше написання коду одним із поданих методів:
|
||||
|
||||
- Клонуйте цей [репозиторій GitHub ](https://github.com/freeCodeCamp/boilerplate-project-metricimpconverter/) і локально завершіть ваш проєкт.
|
||||
- Скористайтеся [ нашим проєктом для початківців на Replit](https://replit.com/github/freeCodeCamp/boilerplate-project-metricimpconverter), щоб завершити ваш проєкт.
|
||||
- Використання будь-якого конструктора сайтів на свій власний вибір для виконання проєкту. Перевірте, що ви зберегли усі файли з нашого репозиторію GitHub.
|
||||
|
||||
По завершенню, впевніться, що робоча демоверсія вашого проєкту знаходиться у відкритому доступі. Потім введіть URL адресу проєкту у поле `Solution Link`. За бажанням, також введіть покликання на вихідний код проєкту у полі `GitHub Link`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
- Завершіть необхідну логіку перетворення у `/controllers/convertHandler.js`
|
||||
- Завершіть необхідні маршрути у `/routes/api.js`
|
||||
- Скопіюйте файл `sample.env` до `.env` і встановіть відповідні змінні
|
||||
- Щоб розпочати тест розкоментуйте `NODE_ENV=test` у вашому файлі `.env`
|
||||
- Щоб провести тести у консолі, використайте команду `npm run test`. Щоб відкрити консоль Replit, натисніть комбінацію клавіш Ctrl+Shift+P (Cmd, якщо на Mac) та введіть "open shell"
|
||||
|
||||
Пропишіть наступні тести у `tests/1_unit-tests.js`:
|
||||
|
||||
- `convertHandler` має правильно прочитати повне введення числа.
|
||||
- `convertHandler` має правильно прочитати введення десяткового числа.
|
||||
- `convertHandler` має правильно прочитати введення дробових даних.
|
||||
- `convertHandler` має правильно прочитати введення дробових даних із десятковим числом.
|
||||
- `convertHandler` має правильно повернути помилку про подвійний дріб (наприклад, `3/2/3`).
|
||||
- `convertHandler` має правильно приймати за замовчуванням числове введення `1`, якщо числове введення не надано.
|
||||
- `convertHandler` має правильно прочитати всі допустимі одиниці вимірів, які вводяться.
|
||||
- `convertHandler` має правильно повернути помилку для недопустимих одиниць виміру, які вводяться.
|
||||
- `convertHandler` має повернути правильну одиницю виміру для кожної допустимої одиниці виміру, яка вводиться.
|
||||
- `convertHandler` має правильно повернути прописаний рядок одиниці виміру для кожної допустимої одиниці виміру, яка вводиться.
|
||||
- `convertHandler` має правильно конвертувати `gal` у `L`.
|
||||
- `convertHandler` має правильно конвертувати `L` у `gal`.
|
||||
- `convertHandler` має правильно конвертувати `mi` у `km`.
|
||||
- `convertHandler` має правильно конвертувати `km` у `mi`.
|
||||
- `convertHandler` має правильно конвертувати `lbs` у `kg`.
|
||||
- `convertHandler` має правильно конвертувати `kg` у `lbs`.
|
||||
|
||||
Пропишіть наступні тести у `tests/2_functional-tests.js`:
|
||||
|
||||
- Конвертуйте допустимі вхідні дані, як запит `10L`: `GET` до `/api/convert`.
|
||||
- Конвертуйте недопустимі вхідні дані, як запит `32g`: `GET` до `/api/convert`.
|
||||
- Конвертуйте недопустиме число, як запит `3/7.2/4kg`: `GET` до `/api/convert`.
|
||||
- Конвертуйте недопустиме число ТА одиницю виміру, як запит `3/7.2/4kilomegagram`: `GET` до `/api/convert`.
|
||||
- Конвертуйте без числа, як запит `kg`: `GET` до `/api/convert`.
|
||||
|
||||
# --hints--
|
||||
|
||||
Ви можете вказати покликання на власний проєкт, а не URL адресу прикладу.
|
||||
|
||||
```js
|
||||
getUserInput => {
|
||||
assert(
|
||||
!/.*\/metric-imperial-converter\.freecodecamp\.rocks/.test(
|
||||
getUserInput('url')
|
||||
)
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит `GET` `/api/convert` із єдиним параметром, який містить прийняте число та одиницю виміру та буде конвертований. (Підказка: розділіть вхідні дані шляхом пошуку індексу першого символу, який позначить початок одиниці виміру)
|
||||
|
||||
```js
|
||||
|
||||
```
|
||||
|
||||
Ви можете конвертувати `'gal'` у `'L'` та навпаки. (1 галон у 3.78541 л)
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data1 = await $.get(getUserInput('url') + '/api/convert?input=1gal');
|
||||
assert.equal(data1.returnNum, 3.78541);
|
||||
assert.equal(data1.returnUnit, 'L');
|
||||
const data2 = await $.get(getUserInput('url') + '/api/convert?input=10gal');
|
||||
assert.equal(data2.returnNum, 37.8541);
|
||||
assert.equal(data2.returnUnit, 'L');
|
||||
const data3 = await $.get(getUserInput('url') + '/api/convert?input=1l');
|
||||
assert.equal(data3.returnNum, 0.26417);
|
||||
assert.equal(data3.returnUnit, 'gal');
|
||||
const data4 = await $.get(getUserInput('url') + '/api/convert?input=10l');
|
||||
assert.equal(data4.returnNum, 2.64172);
|
||||
assert.equal(data4.returnUnit, 'gal');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете конвертувати `'lbs'` у `'kg'` та навпаки. (1 фунт у 0.453592 кг)
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data1 = await $.get(getUserInput('url') + '/api/convert?input=1lbs');
|
||||
assert.equal(data1.returnNum, 0.45359);
|
||||
assert.equal(data1.returnUnit, 'kg');
|
||||
const data2 = await $.get(getUserInput('url') + '/api/convert?input=10lbs');
|
||||
assert.equal(data2.returnNum, 4.53592);
|
||||
assert.equal(data2.returnUnit, 'kg');
|
||||
const data3 = await $.get(getUserInput('url') + '/api/convert?input=1kg');
|
||||
assert.equal(data3.returnNum, 2.20462);
|
||||
assert.equal(data3.returnUnit, 'lbs');
|
||||
const data4 = await $.get(getUserInput('url') + '/api/convert?input=10kg');
|
||||
assert.equal(data4.returnNum, 22.04624);
|
||||
assert.equal(data4.returnUnit, 'lbs');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете конвертувати `'mi'` у `'km'` та навпаки. (1 миля у 1.60934 км)
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data1 = await $.get(getUserInput('url') + '/api/convert?input=1mi');
|
||||
assert.equal(data1.returnNum, 1.60934);
|
||||
assert.equal(data1.returnUnit, 'km');
|
||||
const data2 = await $.get(getUserInput('url') + '/api/convert?input=10mi');
|
||||
assert.equal(data2.returnNum, 16.0934);
|
||||
assert.equal(data2.returnUnit, 'km');
|
||||
const data3 = await $.get(getUserInput('url') + '/api/convert?input=1km');
|
||||
assert.equal(data3.returnNum, 0.62137);
|
||||
assert.equal(data3.returnUnit, 'mi');
|
||||
const data4 = await $.get(getUserInput('url') + '/api/convert?input=10km');
|
||||
assert.equal(data4.returnNum, 6.21373);
|
||||
assert.equal(data4.returnUnit, 'mi');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Написання усіх вхідних одиниць вимірів повинно прийматися, як із великої, так із малої літери, однак повинне повертатися в обох випадках `initUnit` та `returnUnit` у нижньому регістрі. Виключення становить одиниця "liter" - вона має бути повернута у верхньому регістрі `'L'`.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data1 = await $.get(getUserInput('url') + '/api/convert?input=1gal');
|
||||
assert.equal(data1.initUnit, 'gal');
|
||||
assert.equal(data1.returnUnit, 'L');
|
||||
const data2 = await $.get(getUserInput('url') + '/api/convert?input=10L');
|
||||
assert.equal(data2.initUnit, 'L');
|
||||
assert.equal(data2.returnUnit, 'gal');
|
||||
const data3 = await $.get(getUserInput('url') + '/api/convert?input=1l');
|
||||
assert.equal(data3.initUnit, 'L');
|
||||
assert.equal(data3.returnUnit, 'gal');
|
||||
const data4 = await $.get(getUserInput('url') + '/api/convert?input=10KM');
|
||||
assert.equal(data4.initUnit, 'km');
|
||||
assert.equal(data4.returnUnit, 'mi');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо одиниця виміру недопустима - повернути наступне `'invalid unit'`.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data = await $.get(getUserInput('url') + '/api/convert?input=1min');
|
||||
assert(data.error === 'invalid unit' || data === 'invalid unit');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо число недопустиме - повернути наступне `'invalid number'`.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data = await $.get(
|
||||
getUserInput('url') + '/api/convert?input=1//2gal'
|
||||
);
|
||||
assert(data.error === 'invalid number' || data === 'invalid number');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Якщо одиниця виміру і число недопустимі - повернути наступне `'invalid number and unit'`.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data = await $.get(
|
||||
getUserInput('url') + '/api/convert?input=1//2min'
|
||||
);
|
||||
assert(
|
||||
data.error === 'invalid number and unit' ||
|
||||
data === 'invalid number and unit'
|
||||
);
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете використовувати дроби, десяткові числа або і те, і інше у параметрах(до прикладу: 5, 1/2, 2.5/6), але якщо нічого не вказано, то за замовчуванням встановиться 1.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data1 = await $.get(getUserInput('url') + '/api/convert?input=mi');
|
||||
assert.approximately(data1.initNum, 1, 0.001);
|
||||
assert.approximately(data1.returnNum, 1.60934, 0.001);
|
||||
assert.equal(data1.returnUnit, 'km');
|
||||
const data2 = await $.get(getUserInput('url') + '/api/convert?input=1/5mi');
|
||||
assert.approximately(data2.initNum, 1 / 5, 0.1);
|
||||
assert.approximately(data2.returnNum, 0.32187, 0.001);
|
||||
assert.equal(data2.returnUnit, 'km');
|
||||
const data3 = await $.get(
|
||||
getUserInput('url') + '/api/convert?input=1.5/7km'
|
||||
);
|
||||
assert.approximately(data3.initNum, 1.5 / 7, 0.001);
|
||||
assert.approximately(data3.returnNum, 0.13315, 0.001);
|
||||
assert.equal(data3.returnUnit, 'mi');
|
||||
const data4 = await $.get(
|
||||
getUserInput('url') + '/api/convert?input=3/2.7km'
|
||||
);
|
||||
assert.approximately(data4.initNum, 3 / 2.7, 0.001);
|
||||
assert.approximately(data4.returnNum, 0.69041, 0.001);
|
||||
assert.equal(data4.returnUnit, 'mi');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ваше повернення буде складатися із `initNum`, `initUnit`, `returnNum`, `returnUnit` та `string`, що прописують одиниці виміру у форматі`'{initNum} {initUnitString} converts to {returnNum} {returnUnitString}'` із округленим до 5-ти символів після коми результатом.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const data = await $.get(getUserInput('url') + '/api/convert?input=2mi');
|
||||
assert.equal(data.initNum, 2);
|
||||
assert.equal(data.initUnit, 'mi');
|
||||
assert.approximately(data.returnNum, 3.21868, 0.001);
|
||||
assert.equal(data.returnUnit, 'km', 'returnUnit did not match');
|
||||
assert.equal(data.string, '2 miles converts to 3.21868 kilometers');
|
||||
} catch (xhr) {
|
||||
throw new Error(xhr.responseText || xhr.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 16 модульних тестів завершено та успішно пройдено.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
const unitTests = getTests.filter(test => {
|
||||
return !!test.context.match(/Unit Tests/gi);
|
||||
});
|
||||
assert.isAtLeast(unitTests.length, 16, 'At least 16 tests passed');
|
||||
unitTests.forEach(test => {
|
||||
assert.equal(test.state, 'passed', 'Tests in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 5 функціональних тестів завершено та успішно пройдено.
|
||||
|
||||
```js
|
||||
async getUserInput => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
const functTests = getTests.filter(test => {
|
||||
return !!test.context.match(/Functional Tests/gi);
|
||||
});
|
||||
assert.isAtLeast(functTests.length, 5, 'At least 5 tests passed');
|
||||
functTests.forEach(test => {
|
||||
assert.equal(test.state, 'passed', 'Tests in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,220 @@
|
||||
---
|
||||
id: 587d824a367417b2b2512c43
|
||||
title: Особиста бібліотека
|
||||
challengeType: 4
|
||||
forumTopicId: 301571
|
||||
dashedName: personal-library
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Створіть повний пакет JavaScript додаток, функціонально схожий до <https://personal-library.freecodecamp.rocks/>. Робота над цим проєктом бути включати написання коду одним із таких методів:
|
||||
|
||||
- Клонувати [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-library) та локально завершити ваш проєкт.
|
||||
- Використати [наш стартовий проєкт](https://replit.com/github/freeCodeCamp/boilerplate-project-library), щоб завершити ваш проєкт.
|
||||
- Для завершення проєкту використайте вибраний вами розробник сайтів. Не забудьте включити всі файли із нашого репо-сервера GitHub.
|
||||
|
||||
Коли ви завершили, переконайтеся, ця демоверсія вашого проєкту розміщена у відкритому місці. Потім введіть URL-адресу у поле `Solution Link`. Додатково, також вкажіть посилання на вхідний код вашого проєкту у полі `GitHub Link`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
1. Додайте рядок підключення MongoDB до `.env` без дужок як `DB` Приклад: `DB=mongodb://admin:pass@1234.mlab.com:1234/fccpersonallib`
|
||||
2. У вашому файлі `.env` встановіть`NODE_ENV` на `test` без дужок
|
||||
3. Вам потрібно створити усі маршрути в межах `routes/api.js`
|
||||
4. Ви створите усі функціональні тести в `tests/2_functional-tests.js`
|
||||
|
||||
# --hints--
|
||||
|
||||
Ви можете застосувати свій власний проєкт, а не URL-посилання прикладу.
|
||||
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(
|
||||
!/.*\/personal-library\.freecodecamp\.rocks/.test(getUserInput('url'))
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете відправити запит <b>POST</b> на `/api/books` з `title` як частину форми даних, щоб додати книгу. Зворотною відповіддю буде об'єкт з `title` та унікальним `_id` як ключі доступу. Якщо `title` не включений у запит, то відповідь повинна бути рядком `missing required field title`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let data1 = await $.post(getUserInput('url') + '/api/books', {
|
||||
title: 'Faux Book 1'
|
||||
});
|
||||
assert.isObject(data1);
|
||||
assert.property(data1, 'title');
|
||||
assert.equal(data1.title, 'Faux Book 1');
|
||||
assert.property(data1, '_id');
|
||||
let data2 = await $.post(getUserInput('url') + '/api/books');
|
||||
assert.isString(data2);
|
||||
assert.equal(data2, 'missing required field title');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит <b>GET</b> до `/api/books` та отримати відповідь JSON у вигляді усіх книг. Відповідь JSON буде масивом об'єктів з кожним об'єктом (книгою), який складається з `title`, `_id` та `commentcount`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let url = getUserInput('url') + '/api/books';
|
||||
let a = $.post(url, { title: 'Faux Book A' });
|
||||
let b = $.post(url, { title: 'Faux Book B' });
|
||||
let c = $.post(url, { title: 'Faux Book C' });
|
||||
await Promise.all([a, b, c]).then(async () => {
|
||||
let data = await $.get(url);
|
||||
assert.isArray(data);
|
||||
assert.isAtLeast(data.length, 3);
|
||||
data.forEach((book) => {
|
||||
assert.isObject(book);
|
||||
assert.property(book, 'title');
|
||||
assert.isString(book.title);
|
||||
assert.property(book, '_id');
|
||||
assert.property(book, 'commentcount');
|
||||
assert.isNumber(book.commentcount);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит <b>GET</b> до `/api/books/{_id}`, щоб отримати один об'єкт в книзі, який містить властивості `title`, `_id` та `comments` (пустий масив, якщо немає коментарів). Якщо не знайдено книги, поверніться в рядок `no book exists`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let url = getUserInput('url') + '/api/books';
|
||||
let noBook = await $.get(url + '/5f665eb46e296f6b9b6a504d');
|
||||
assert.isString(noBook);
|
||||
assert.equal(noBook, 'no book exists');
|
||||
let sampleBook = await $.post(url, { title: 'Faux Book Alpha' });
|
||||
assert.isObject(sampleBook);
|
||||
let bookId = sampleBook._id;
|
||||
let bookQuery = await $.get(url + '/' + bookId);
|
||||
assert.isObject(bookQuery);
|
||||
assert.property(bookQuery, 'title');
|
||||
assert.equal(bookQuery.title, 'Faux Book Alpha');
|
||||
assert.property(bookQuery, 'comments');
|
||||
assert.isArray(bookQuery.comments);
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит <b>POST</b>, який містить `comment`, як частину корпусу даних до `/api/books/{_id}`, щоб додати коментар до книжки. Отримана відповідь - об'єкт книги, аналогічний до <b>GET</b> `/api/books/{_id}`, запиту в попередньому тесті. Якщо `comment` не включено в запит, поверніться до рядка `missing required field comment`. Якщо не знайдено книги, поверніться до рядка `no book exists`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let url = getUserInput('url') + '/api/books';
|
||||
let commentTarget = await $.post(url, { title: 'Notable Book' });
|
||||
assert.isObject(commentTarget);
|
||||
let bookId = commentTarget._id;
|
||||
let bookCom1 = await $.post(url + '/' + bookId, {
|
||||
comment: 'This book is fab!'
|
||||
});
|
||||
let bookCom2 = await $.post(url + '/' + bookId, {
|
||||
comment: 'I did not care for it'
|
||||
});
|
||||
assert.isObject(bookCom2);
|
||||
assert.property(bookCom2, '_id');
|
||||
assert.property(bookCom2, 'title');
|
||||
assert.property(bookCom2, 'comments');
|
||||
assert.lengthOf(bookCom2.comments, 2);
|
||||
bookCom2.comments.forEach((comment) => {
|
||||
assert.isString(comment);
|
||||
assert.oneOf(comment, ['This book is fab!', 'I did not care for it']);
|
||||
});
|
||||
let commentErr = await $.post(url + '/' + bookId);
|
||||
assert.isString(commentErr);
|
||||
assert.equal(commentErr, 'missing required field comment');
|
||||
let failingComment = await $.post(url + '/5f665eb46e296f6b9b6a504d', {
|
||||
comment: 'Never Seen Comment'
|
||||
});
|
||||
assert.isString(failingComment);
|
||||
assert.equal(failingComment, 'no book exists');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит <b>DELETE</b> до `/api/books/{_id}`, щоб видалити книги з колекції. Отримана відповідь - рядок `delete successful`, якщо видалено успішно. Якщо книги не знайдено, поверніться до рядка `no book exists`.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
let url = getUserInput('url') + '/api/books';
|
||||
let deleteTarget = await $.post(url, { title: 'Deletable Book' });
|
||||
assert.isObject(deleteTarget);
|
||||
let bookId = deleteTarget._id;
|
||||
let doDelete = await $.ajax({ url: url + '/' + bookId, type: 'DELETE' });
|
||||
assert.isString(doDelete);
|
||||
assert.equal(doDelete, 'delete successful');
|
||||
let failingDelete = await $.ajax({
|
||||
url: url + '/5f665eb46e296f6b9b6a504d',
|
||||
type: 'DELETE'
|
||||
});
|
||||
assert.isString(failingDelete);
|
||||
assert.equal(failingDelete, 'no book exists');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете надіслати запит <b>DELETE</b> до `/api/books`, щоб видалити усі книги з інформаційної бази. Отримана відповідь - рядок `'complete delete successful`, якщо видалено успішно.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const deleteAll = await $.ajax({
|
||||
url: getUserInput('url') + '/api/books',
|
||||
type: 'DELETE'
|
||||
});
|
||||
assert.isString(deleteAll);
|
||||
assert.equal(deleteAll, 'complete delete successful');
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 10 необхідних функціональних тестів завершено й успішно здано.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
assert.isAtLeast(getTests.length, 10, 'At least 10 tests passed');
|
||||
getTests.forEach((test) => {
|
||||
assert.equal(test.state, 'passed', 'Test in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
@@ -0,0 +1,379 @@
|
||||
---
|
||||
id: 5e601bf95ac9d0ecd8b94afd
|
||||
title: Програма для розв'язування судоку
|
||||
challengeType: 4
|
||||
forumTopicId: 462357
|
||||
dashedName: sudoku-solver
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Створіть повний пакет додатку JavaScript, який функціонально схожий до цього: <https://sudoku-solver.freecodecamp.rocks/>. Робота над цим проєктом включатиме написання коду одним з наступних методів:
|
||||
|
||||
- Клонуйте цей [репозиторій GitHub ](https://github.com/freecodecamp/boilerplate-project-sudoku-solver) і локально завершіть ваш проєкт.
|
||||
- Використовуйте [наш Replit проєкт](https://replit.com/github/freeCodeCamp/boilerplate-project-sudoku-solver), щоб завершити ваш проєкт.
|
||||
- Для завершення проєкту використовуйте вибраний вами розробник сайтів. Переконайтеся, що зберегли усі файли з нашого репозиторію GitHub.
|
||||
|
||||
Коли ви завершили, переконайтеся, що ця демоверсія вашого проєкту розміщена у відкритому місці. Потім введіть URL-адресу у поле `Solution Link`. При необхідності, також введіть посилання на джерело коду вашого проєкту у полі `GitHub Link`.
|
||||
|
||||
# --instructions--
|
||||
|
||||
- Уся логіка головоломки може ввійти в `/controllers/sudoku-solver.js`
|
||||
- Функція `validate` повинна взяти вказаний рядок головоломки й перевірити його, чи є в ньому 81 допустимий символ для входу.
|
||||
- Функції `check` повинні бути перевіреними відповідно до статусу *current* на сторінці.
|
||||
- Функція `solve` повинна опрацьовувати вирішення будь-якого заданого рядка допустимої головоломки, а не тільки входу та вирішенню тесту. Щоб вирішити це, ви маєте розшифрувати логіку.
|
||||
- Вся логіка маршрутизації може переміститися в `/routes/api.js`
|
||||
- Подивіться на файл `puzzle-strings.js` в `/controllers` для деяких зразків головоломок, які має розв'язати ваша програма
|
||||
- Щоб запустити завдання на цій сторінці, налаштуйте `NODE_ENV` на `test` без лапок у файлі `.env`
|
||||
- Щоб запустити завдання, використовуйте команду `npm run test`. Щоб відкрити консоль Replit, натисніть Ctrl+Shift+P (Cmd, якщо з Mac) та наберіть "open shell"
|
||||
|
||||
Напишіть наступні тести в `tests/1_unit-tests.js`:
|
||||
|
||||
- Логіка обробляє допустимий рядок з 81 символом
|
||||
- Логіка опрацьовує рядок головоломки з доступними символами (не 1-9 чи `.`)
|
||||
- Логіка опрацьовує рядок головоломки, який не містить 81 символ
|
||||
- Логіка опрацьовує допустиме розміщення рядка
|
||||
- Логіка опрацьовує недопустиме розміщення рядка
|
||||
- Логіка опрацьовує допустиме розміщення стовпця
|
||||
- Логіка опрацьовує недопустиме розміщення стовпця
|
||||
- Логіка опрацьовує допустиме розміщення області (сітка 3x3)
|
||||
- Логіка опрацьовує недопустиме розміщення області (сітка 3x3)
|
||||
- Допустимі рядки головоломки передають вирішення
|
||||
- Недопустимі рядки головоломки не передають вирішення
|
||||
- Розв'язувач повертає очікуване рішення для незавершеного завдання
|
||||
|
||||
Напишіть наступні тести в `tests/2_functional-tests.js`
|
||||
|
||||
- Розв'яжіть головоломку з допустимим рядком головоломки: запит POST на `/api/solve`
|
||||
- Розв'яжіть головоломку з пропущеним рядком головоломки: запит POST на `/api/solve`
|
||||
- Розв'яжіть головоломку з недопустимими символами: запит POST на `/api/solve`
|
||||
- Розв'яжіть головоломку з неправильною довжиною: запит POST на `/api/solve`
|
||||
- Розв'яжіть головоломку, яка не може бути розв'язана: запит POST на `/api/solve`
|
||||
- Перевірте розміщення головоломки з усіма полями: запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з одним конфліктом: запит POST на `/api/check`
|
||||
- Перевірте головоломку з декількома конфліктами: запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з усіма конфліктами: запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з пропущеними необхідними полями: запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з недопустимими символами: запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з неправильною довжиною: запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з недопустимим розміщенням позиції (координат): запит POST на `/api/check`
|
||||
- Перевірте розміщення головоломки з недопустимим розміщенням значення: запит POST на `/api/check`
|
||||
|
||||
# --hints--
|
||||
|
||||
Вам необхідно вказати свій власний проєкт, а не приклад URL-адреси.
|
||||
|
||||
```js
|
||||
(getUserInput) => {
|
||||
const url = getUserInput('url');
|
||||
assert(!/.*\/sudoku-solver\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете `POST` `/api/solve` з даними форми, які містять `puzzle`, який буде рядком з комбінації чисел (1-9) та періодів `.`, щоб зобразити пробіли. Повернений об'єкт буде містити властивість `solution` з вирішеною головоломкою.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
const input =
|
||||
'..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output =
|
||||
'769235418851496372432178956174569283395842761628713549283657194516924837947381625';
|
||||
const data = await fetch(getUserInput('url') + '/api/solve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'solution');
|
||||
assert.equal(parsed.solution, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо об'єкт, представлений в `/api/solve` відсутній `puzzle`, зворотне значення буде `{error: 'Required field missing'}`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const data = await fetch(getUserInput('url') + '/api/solve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ notpuzzle: input })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо головоломка, представлена в `/api/solve` містить значення, які не є числами чи періодами, то зворотне значення буде `{ error: 'Invalid characters in puzzle' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const data = await fetch(getUserInput('url') + '/api/solve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо головоломка, представлена в `/api/solve` містить більше чи менше ніж 81 символу, то зворотне значення буде `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
const input =
|
||||
'9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..';
|
||||
const output = 'Expected puzzle to be 81 characters long';
|
||||
const data = await fetch(getUserInput('url') + '/api/solve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо головоломка, представлена в `/api/solve` недопустима чи не може бути розв'язаною, то зворотне значення буде `{ error: 'Puzzle cannot be solved' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const data = await fetch(getUserInput('url') + '/api/solve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Ви можете `POST` на об'єкт `/api/check`, який містить `puzzle`, `coordinate`, і `value`, де `coordinate` - букви A-I, які позначають рядок, потім числа 1-9, які позначають стовпець і `value` - числа 1-9.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const value = '7';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'valid');
|
||||
assert.isTrue(parsed.valid);
|
||||
};
|
||||
```
|
||||
|
||||
Зворотне значення від`POST` до `/api/check` буде об'єкт, який містить властивість `valid`, яка є `true` якщо номер може бути розміщеним у запропоновану координату і `false` якщо ні. Якщо неправильно, то повернений об'єкт також буде містити властивість `conflict`, яка є масивом з рядками `"row"`, `"column"`, і /or `"region"`, в залежності від того, що робить розміщення недопустимим.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const value = '1';
|
||||
const conflict = ['row', 'column'];
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'valid');
|
||||
assert.isFalse(parsed.valid);
|
||||
assert.property(parsed, 'conflict');
|
||||
assert.include(parsed.conflict, 'row');
|
||||
assert.include(parsed.conflict, 'column');
|
||||
};
|
||||
```
|
||||
|
||||
Якщо `value` представлене в `/api/check` уже поміщене в `puzzle` на ту `coordinate`, то зворотне значення буде об'єкт, в якому є властивість `valid` з `true`, якщо `value` не суперечливий.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const value = '2';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'valid');
|
||||
assert.isTrue(parsed.valid);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо головоломка представлена в `/api/check` містить значення, які не є числами чи періодами, зворотне значення буде `{ error: 'Invalid characters in puzzle' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const value = '1';
|
||||
const output = 'Invalid characters in puzzle';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо головоломка представлена в `/api/check` містить більше чи менше ніж 81 символ, то зворотне значення буде `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const value = '1';
|
||||
const output = 'Expected puzzle to be 81 characters long';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо об'єкт представлений в `/api/check` немає `puzzle`, `coordinate` чи `value`, то зворотне значення буде `{ error: Required field(s) missing }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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(s) missing';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо координата представлена в `api/check` не вказує на чинну ланку сітки, то зворотне значення буде `{ error: 'Invalid coordinate'}`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const coordinate = 'XZ18';
|
||||
const value = '7';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Якщо `value` представлене в `/api/check` не є числом між 1 і 9, то зворотне значення буде `{ error: 'Invalid value' }`
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
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';
|
||||
const coordinate = 'A1';
|
||||
const value = 'X';
|
||||
const data = await fetch(getUserInput('url') + '/api/check', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ puzzle: input, coordinate, value })
|
||||
});
|
||||
const parsed = await data.json();
|
||||
assert.property(parsed, 'error');
|
||||
assert.equal(parsed.error, output);
|
||||
};
|
||||
```
|
||||
|
||||
Усі 12 модульних тестів завершено та успішно пройдено. Дивіться `/tests/1_unit-tests.js` для очікуваного поводження об'єкту, вам слід написати тести.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
const units = getTests.filter((el) => el.context.includes('UnitTests'));
|
||||
assert.isAtLeast(units.length, 12, 'At least 12 tests passed');
|
||||
units.forEach((test) => {
|
||||
assert.equal(test.state, 'passed', 'Test in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Усі 14 функціональних тестів завершено та успішно пройдено. Дивіться `/tests/2_functional-tests.js` для функціональності, для якої вам слід написати тести.
|
||||
|
||||
```js
|
||||
async (getUserInput) => {
|
||||
try {
|
||||
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
||||
assert.isArray(getTests);
|
||||
const funcs = getTests.filter((el) =>
|
||||
el.context.includes('Functional Tests')
|
||||
);
|
||||
assert.isAtLeast(funcs.length, 14, 'At least 14 tests passed');
|
||||
funcs.forEach((test) => {
|
||||
assert.equal(test.state, 'passed', 'Test in Passed State');
|
||||
assert.isAtLeast(
|
||||
test.assertions.length,
|
||||
1,
|
||||
'At least one assertion per test'
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err.responseText || err.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
Backend challenges don't need solutions,
|
||||
because they would need to be tested against a full working project.
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
Reference in New Issue
Block a user