Feat: add new Markdown parser (#39800)
and change all the challenges to new `md` format.
This commit is contained in:
committed by
GitHub
parent
a07f84c8ec
commit
0bd52f8bd1
@@ -4,305 +4,329 @@ title: American British Translator
|
||||
challengeType: 4
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id='description'>
|
||||
Build a full stack JavaScript app that is functionally similar to this: <a href='https://american-british-translator.freecodecamp.rocks/' target='_blank'>https://american-british-translator.freecodecamp.rocks/</a>. Working on this project will involve you writing your code using one of the following methods:
|
||||
# --description--
|
||||
|
||||
- Clone <a href='https://github.com/freeCodeCamp/boilerplate-project-american-british-english-translator/' target='_blank'>this GitHub repo</a> and complete your project locally.
|
||||
- Use <a href='https://repl.it/github/freeCodeCamp/boilerplate-project-american-british-english-translator' target='_blank'>our repl.it starter project</a> to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
Build a full stack JavaScript app that is functionally similar to this: <https://american-british-translator.freecodecamp.rocks/>. Working on this project will involve you writing your code using one of the following methods:
|
||||
|
||||
- Clone [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-american-british-english-translator/) and complete your project locally.
|
||||
- Use [our repl.it starter project](https://repl.it/github/freeCodeCamp/boilerplate-project-american-british-english-translator) to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
|
||||
When you are done, make sure a working demo of your project is hosted somewhere public. Then submit the URL to it in the `Solution Link` field. Optionally, also submit a link to your project's source code in the `GitHub Link` field.
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id='instructions'>
|
||||
# --instructions--
|
||||
|
||||
- All logic can go into `/components/translator.js`
|
||||
- Complete the `/api/translate` route in `/routes/api.js`
|
||||
- Create all of the unit/functional tests in `tests/1_unit-tests.js` and `tests/2_functional-tests.js`
|
||||
- See the JavaScript files in `/components` for the different spelling and terms your application should translate
|
||||
- To run the tests on Repl.it, set `NODE_ENV` to `test` without quotes in the `.env` file
|
||||
- To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
|
||||
- All logic can go into `/components/translator.js`
|
||||
- Complete the `/api/translate` route in `/routes/api.js`
|
||||
- Create all of the unit/functional tests in `tests/1_unit-tests.js` and `tests/2_functional-tests.js`
|
||||
- See the JavaScript files in `/components` for the different spelling and terms your application should translate
|
||||
- To run the tests on Repl.it, set `NODE_ENV` to `test` without quotes in the `.env` file
|
||||
- To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
|
||||
|
||||
Write the following tests in `tests/1_unit-tests.js`:
|
||||
|
||||
- Translate <code>Mangoes are my favorite fruit.</code> to British English
|
||||
- Translate <code>I ate yogurt for breakfast.</code> to British English
|
||||
- Translate <code>We had a party at my friend's condo.</code> to British English
|
||||
- Translate <code>Can you toss this in the trashcan for me?</code> to British English
|
||||
- Translate <code>The parking lot was full.</code> to British English
|
||||
- Translate <code>Like a high tech Rube Goldberg machine.</code> to British English
|
||||
- Translate <code>To play hooky means to skip class or work.</code> to British English
|
||||
- Translate <code>No Mr. Bond, I expect you to die.</code> to British English
|
||||
- Translate <code>Dr. Grosh will see you now.</code> to British English
|
||||
- Translate <code>Lunch is at 12:15 today.</code> to British English
|
||||
- Translate <code>We watched the footie match for a while.</code> to American English
|
||||
- Translate <code>Paracetamol takes up to an hour to work.</code> to American English
|
||||
- Translate <code>First, caramelise the onions.</code> to American English
|
||||
- Translate <code>I spent the bank holiday at the funfair.</code> to American English
|
||||
- Translate <code>I had a bicky then went to the chippy.</code> to American English
|
||||
- Translate <code>I've just got bits and bobs in my bum bag.</code> to American English
|
||||
- Translate <code>The car boot sale at Boxted Airfield was called off.</code> to American English
|
||||
- Translate <code>Have you met Mrs Kalyani?</code> to American English
|
||||
- Translate <code>Prof Joyner of King's College, London.</code> to American English
|
||||
- Translate <code>Tea time is usually around 4 or 4.30.</code> to American English
|
||||
- Highlight translation in <code>Mangoes are my favorite fruit.</code>
|
||||
- Highlight translation in <code>I ate yogurt for breakfast.</code>
|
||||
- Highlight translation in <code>We watched the footie match for a while.</code>
|
||||
- Highlight translation in <code>Paracetamol takes up to an hour to work.</code>
|
||||
- Translate `Mangoes are my favorite fruit.` to British English
|
||||
- Translate `I ate yogurt for breakfast.` to British English
|
||||
- Translate `We had a party at my friend's condo.` to British English
|
||||
- Translate `Can you toss this in the trashcan for me?` to British English
|
||||
- Translate `The parking lot was full.` to British English
|
||||
- Translate `Like a high tech Rube Goldberg machine.` to British English
|
||||
- Translate `To play hooky means to skip class or work.` to British English
|
||||
- Translate `No Mr. Bond, I expect you to die.` to British English
|
||||
- Translate `Dr. Grosh will see you now.` to British English
|
||||
- Translate `Lunch is at 12:15 today.` to British English
|
||||
- Translate `We watched the footie match for a while.` to American English
|
||||
- Translate `Paracetamol takes up to an hour to work.` to American English
|
||||
- Translate `First, caramelise the onions.` to American English
|
||||
- Translate `I spent the bank holiday at the funfair.` to American English
|
||||
- Translate `I had a bicky then went to the chippy.` to American English
|
||||
- Translate `I've just got bits and bobs in my bum bag.` to American English
|
||||
- Translate `The car boot sale at Boxted Airfield was called off.` to American English
|
||||
- Translate `Have you met Mrs Kalyani?` to American English
|
||||
- Translate `Prof Joyner of King's College, London.` to American English
|
||||
- Translate `Tea time is usually around 4 or 4.30.` to American English
|
||||
- Highlight translation in `Mangoes are my favorite fruit.`
|
||||
- Highlight translation in `I ate yogurt for breakfast.`
|
||||
- Highlight translation in `We watched the footie match for a while.`
|
||||
- Highlight translation in `Paracetamol takes up to an hour to work.`
|
||||
|
||||
Write the following tests in `tests/2_functional-tests.js`:
|
||||
|
||||
- Translation with text and locale fields: POST request to `/api/translate`
|
||||
- Translation with text and invalid locale field: POST request to `/api/translate`
|
||||
- Translation with missing text field: POST request to `/api/translate`
|
||||
- Translation with missing locale field: POST request to `/api/translate`
|
||||
- Translation with empty text: POST request to `/api/translate`
|
||||
- Translation with text that needs no translation: POST request to `/api/translate`
|
||||
- Translation with text and locale fields: POST request to `/api/translate`
|
||||
- Translation with text and invalid locale field: POST request to `/api/translate`
|
||||
- Translation with missing text field: POST request to `/api/translate`
|
||||
- Translation with missing locale field: POST request to `/api/translate`
|
||||
- Translation with empty text: POST request to `/api/translate`
|
||||
- Translation with text that needs no translation: POST request to `/api/translate`
|
||||
|
||||
</section>
|
||||
# --hints--
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: I can provide my own project, not the example URL.
|
||||
testString: |
|
||||
getUserInput => {
|
||||
assert(!/.*\/american-british-translator\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
}
|
||||
|
||||
- text: You can <code>POST</code> to <code>/api/translate</code> with a body containing <code>text</code> with the text to translate and <code>locale</code> with either <code>american-to-british</code> or <code>british-to-american</code>. The returned object should contain the submitted <code>text</code> and <code>translation</code> with the translated text.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: The <code>/api/translate</code> route should handle the way time is written in American and British English. For example, ten thirty is written as "10.30" in British English and "10:30" in American English.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: The <code>/api/translate</code> route should also handle the way titles/honorifics are abbreviated in American and British English. For example, Doctor Wright is abbreviated as "Dr Wright" in British English and "Dr. Wright" in American English. See <code>/public/american-to-british-titles.js</code> for the different titles your application should handle.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: Wrap any translated spelling or terms with <code><span class="highlight">...</span></code> tags so they appear in green.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: If one or more of the required fields is missing, return <code>{ error: 'Required field(s) missing' }</code>.
|
||||
testString: "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: If <code>text</code> is empty, return <code>{ error: 'No text to translate' }</code>
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: If <code>locale</code> does not match one of the two specified locales, return <code>{ error: 'Invalid value for locale field' }</code>.
|
||||
testString: "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: If <code>text</code> requires no translation, return <code>"Everything looks good to me!"</code> for the <code>translation</code> value.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: All 24 unit tests are complete and passing. See `/tests/1_unit-tests.js` for the expected behavior you should write tests for.
|
||||
testString: "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 ->/ig);
|
||||
});
|
||||
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);
|
||||
}
|
||||
}"
|
||||
- text: All 6 functional tests are complete and passing. See `/tests/2_functional-tests.js` for the functionality you should write tests for.
|
||||
testString: "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 ->/ig);
|
||||
});
|
||||
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);
|
||||
}
|
||||
}"
|
||||
I can provide my own project, not the example URL.
|
||||
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(
|
||||
!/.*\/american-british-translator\.freecodecamp\.rocks/.test(
|
||||
getUserInput('url')
|
||||
)
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
You can `POST` to `/api/translate` with a body containing `text` with the text to translate and `locale` with either `american-to-british` or `british-to-american`. The returned object should contain the submitted `text` and `translation` with the translated text.
|
||||
|
||||
## Challenge Seed
|
||||
<section id='challengeSeed'>
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
The `/api/translate` route should handle the way time is written in American and British English. For example, ten thirty is written as "10.30" in British English and "10:30" in American English.
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `/api/translate` route should also handle the way titles/honorifics are abbreviated in American and British English. For example, Doctor Wright is abbreviated as "Dr Wright" in British English and "Dr. Wright" in American English. See `/public/american-to-british-titles.js` for the different titles your application should handle.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Wrap any translated spelling or terms with `<span class="highlight">...</span>` tags so they appear in green.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If one or more of the required fields is missing, return `{ 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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If `text` is empty, return `{ 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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If `locale` does not match one of the two specified locales, return `{ 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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If `text` requires no translation, return `"Everything looks good to me!"` for the `translation` value.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 24 unit tests are complete and passing. See `/tests/1_unit-tests.js` for the expected behavior you should write tests for.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 6 functional tests are complete and passing. See `/tests/2_functional-tests.js` for the functionality you should write tests for.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
@@ -311,5 +335,3 @@ tests:
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@@ -5,299 +5,361 @@ challengeType: 4
|
||||
forumTopicId: 301569
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id='description'>
|
||||
Build a full stack JavaScript app that is functionally similar to this: <a href="https://issue-tracker.freecodecamp.rocks/" target="_blank">https://issue-tracker.freecodecamp.rocks/</a>. Working on this project will involve you writing your code using one of the following methods:
|
||||
# --description--
|
||||
|
||||
- Clone <a href='https://github.com/freeCodeCamp/boilerplate-project-issuetracker/' target='_blank'>this GitHub repo</a> and complete your project locally.
|
||||
- Use <a href="https://repl.it/github/freeCodeCamp/boilerplate-project-issuetracker" target='_blank'>this repl.it starter project</a> to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
Build a full stack JavaScript app that is functionally similar to this: <https://issue-tracker.freecodecamp.rocks/>. Working on this project will involve you writing your code using one of the following methods:
|
||||
|
||||
- Clone [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-issuetracker/) and complete your project locally.
|
||||
- Use [this repl.it starter project](https://repl.it/github/freeCodeCamp/boilerplate-project-issuetracker) to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
|
||||
When you are done, make sure a working demo of your project is hosted somewhere public. Then submit the URL to it in the `Solution Link` field. Optionally, also submit a link to your project's source code in the `GitHub Link` field.
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id='instructions'>
|
||||
# --instructions--
|
||||
|
||||
- Complete the necessary routes in `/routes/api.js`
|
||||
- Create all of the functional tests in `tests/2_functional-tests.js`
|
||||
- Copy the `sample.env` file to `.env` and set the variables appropriately
|
||||
- To run the tests uncomment `NODE_ENV=test` in your `.env` file
|
||||
- To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
|
||||
- Complete the necessary routes in `/routes/api.js`
|
||||
- Create all of the functional tests in `tests/2_functional-tests.js`
|
||||
- Copy the `sample.env` file to `.env` and set the variables appropriately
|
||||
- To run the tests uncomment `NODE_ENV=test` in your `.env` file
|
||||
- To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
|
||||
|
||||
Write the following tests in `tests/2_functional-tests.js`:
|
||||
|
||||
- Create an issue with every field: POST request to `/api/issues/{project}`
|
||||
- Create an issue with only required fields: POST request to `/api/issues/{project}`
|
||||
- Create an issue with missing required fields: POST request to `/api/issues/{project}`
|
||||
- View issues on a project: GET request to `/api/issues/{project}`
|
||||
- View issues on a project with one filter: GET request to `/api/issues/{project}`
|
||||
- View issues on a project with multiple filters: GET request to `/api/issues/{project}`
|
||||
- Update one field on an issue: PUT request to `/api/issues/{project}`
|
||||
- Update multiple fields on an issue: PUT request to `/api/issues/{project}`
|
||||
- Update an issue with missing `_id`: PUT request to `/api/issues/{project}`
|
||||
- Update an issue with no fields to update: PUT request to `/api/issues/{project}`
|
||||
- Update an issue with an invalid `_id`: PUT request to `/api/issues/{project}`
|
||||
- Delete an issue: DELETE request to `/api/issues/{project}`
|
||||
- Delete an issue with an invalid `_id`: DELETE request to `/api/issues/{project}`
|
||||
- Delete an issue with missing `_id`: DELETE request to `/api/issues/{project}`
|
||||
- Create an issue with every field: POST request to `/api/issues/{project}`
|
||||
- Create an issue with only required fields: POST request to `/api/issues/{project}`
|
||||
- Create an issue with missing required fields: POST request to `/api/issues/{project}`
|
||||
- View issues on a project: GET request to `/api/issues/{project}`
|
||||
- View issues on a project with one filter: GET request to `/api/issues/{project}`
|
||||
- View issues on a project with multiple filters: GET request to `/api/issues/{project}`
|
||||
- Update one field on an issue: PUT request to `/api/issues/{project}`
|
||||
- Update multiple fields on an issue: PUT request to `/api/issues/{project}`
|
||||
- Update an issue with missing `_id`: PUT request to `/api/issues/{project}`
|
||||
- Update an issue with no fields to update: PUT request to `/api/issues/{project}`
|
||||
- Update an issue with an invalid `_id`: PUT request to `/api/issues/{project}`
|
||||
- Delete an issue: DELETE request to `/api/issues/{project}`
|
||||
- Delete an issue with an invalid `_id`: DELETE request to `/api/issues/{project}`
|
||||
- Delete an issue with missing `_id`: DELETE request to `/api/issues/{project}`
|
||||
|
||||
</section>
|
||||
# --hints--
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
You can provide your own project, not the example URL.
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: You can provide your own project, not the example URL.
|
||||
testString: "
|
||||
getUserInput => {
|
||||
assert(!/.*\\/issue-tracker\\.freecodecamp\\.rocks/.test(getUserInput('url')));
|
||||
}"
|
||||
- text: You can send a `POST` request to `/api/issues/{projectname}` with form data containing the required fields `issue_title`, `issue_text`, `created_by`, and optionally `assigned_to` and `status_text`.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: The `POST` request to `/api/issues/{projectname}` will return the created object, and must include all of the submitted fields. Excluded optional fields will be returned as empty strings. Additionally, include `created_on` (date/time), `updated_on` (date/time), `open` (boolean, `true` for open - default value, `false` for closed), and `_id`.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: If you send a `POST` request to `/api/issues/{projectname}` without the required fields, returned will be the error `{ error: 'required field(s) missing' }`
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: You can send a `GET` request to `/api/issues/{projectname}` for an array of all issues for that specific `projectname`, with all the fields present for each issue.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: You can send a `GET` request to `/api/issues/{projectname}` and filter the request by also passing along any field and value as a URL query (ie. `/api/issues/{project}?open=false`). You can pass one or more field/value pairs at once.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: You can send a `PUT` request to `/api/issues/{projectname}` with an `_id` and one or more fields to update. On success, the `updated_on` field should be updated, and returned should be `{ result: 'successfully updated', '_id': _id }`.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: When the `PUT` request sent to `/api/issues/{projectname}` does not include an `_id`, the return value is `{ error: 'missing _id' }`.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: When the `PUT` request sent to `/api/issues/{projectname}` does not include update fields, the return value is `{ error: 'no update field(s) sent', '_id': _id }`. On any other error, the return value is `{ error: 'could not update', '_id': _id }`.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: You can send a `DELETE` request to `/api/issues/{projectname}` with an `_id` to delete an issue. If no `_id` is sent, the return value is `{ error: 'missing _id' }`. On success, the return value is `{ result: 'successfully deleted', '_id': _id }`. On failure, the return value is `{ error: 'could not delete', '_id': _id }`.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
- text: All 14 functional tests are complete and passing.
|
||||
testString: '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);
|
||||
}
|
||||
}'
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(!/.*\/issue-tracker\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
You can send a `POST` request to `/api/issues/{projectname}` with form data containing the required fields `issue_title`, `issue_text`, `created_by`, and optionally `assigned_to` and `status_text`.
|
||||
|
||||
## Challenge Seed
|
||||
<section id='challengeSeed'>
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
The `POST` request to `/api/issues/{projectname}` will return the created object, and must include all of the submitted fields. Excluded optional fields will be returned as empty strings. Additionally, include `created_on` (date/time), `updated_on` (date/time), `open` (boolean, `true` for open - default value, `false` for closed), and `_id`.
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If you send a `POST` request to `/api/issues/{projectname}` without the required fields, returned will be the error `{ error: 'required field(s) missing' }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `GET` request to `/api/issues/{projectname}` for an array of all issues for that specific `projectname`, with all the fields present for each issue.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `GET` request to `/api/issues/{projectname}` and filter the request by also passing along any field and value as a URL query (ie. `/api/issues/{project}?open=false`). You can pass one or more field/value pairs at once.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `PUT` request to `/api/issues/{projectname}` with an `_id` and one or more fields to update. On success, the `updated_on` field should be updated, and returned should be `{ result: 'successfully updated', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `PUT` request sent to `/api/issues/{projectname}` does not include an `_id`, the return value is `{ 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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `PUT` request sent to `/api/issues/{projectname}` does not include update fields, the return value is `{ error: 'no update field(s) sent', '_id': _id }`. On any other error, the return value is `{ error: 'could not update', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a `DELETE` request to `/api/issues/{projectname}` with an `_id` to delete an issue. If no `_id` is sent, the return value is `{ error: 'missing _id' }`. On success, the return value is `{ result: 'successfully deleted', '_id': _id }`. On failure, the return value is `{ error: 'could not delete', '_id': _id }`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 14 functional tests are complete and passing.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
@@ -306,5 +368,3 @@ tests:
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@@ -5,223 +5,275 @@ challengeType: 4
|
||||
forumTopicId: 301570
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id='description'>
|
||||
Build a full stack JavaScript app that is functionally similar to this: <a href="https://metric-imperial-converter.freecodecamp.rocks/" target="_blank">https://metric-imperial-converter.freecodecamp.rocks/</a>. Working on this project will involve you writing your code using one of the following methods:
|
||||
# --description--
|
||||
|
||||
- Clone <a href='https://github.com/freeCodeCamp/boilerplate-project-metricimpconverter/' target='_blank'>this GitHub repo</a> and complete your project locally.
|
||||
- Use <a href='https://repl.it/github/freeCodeCamp/boilerplate-project-metricimpconverter' target='_blank'>our repl.it starter project</a> to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
Build a full stack JavaScript app that is functionally similar to this: <https://metric-imperial-converter.freecodecamp.rocks/>. Working on this project will involve you writing your code using one of the following methods:
|
||||
|
||||
- Clone [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-metricimpconverter/) and complete your project locally.
|
||||
- Use [our repl.it starter project](https://repl.it/github/freeCodeCamp/boilerplate-project-metricimpconverter) to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
|
||||
When you are done, make sure a working demo of your project is hosted somewhere public. Then submit the URL to it in the `Solution Link` field. Optionally, also submit a link to your project's source code in the `GitHub Link` field.
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id='instructions'>
|
||||
# --hints--
|
||||
|
||||
</section>
|
||||
I can provide my own project, not the example URL.
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: I can provide my own project, not the example URL.
|
||||
testString: |
|
||||
getUserInput => {
|
||||
assert(!/.*\/metric-imperial-converter\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
}
|
||||
- text: 'I can GET /api/convert with a single parameter containing an accepted number and unit and have it converted. (Hint: Split the input by looking for the index of the first character which will mark the start of the unit)'
|
||||
testString: ''
|
||||
- text: I can convert <code>'gal'</code> to <code>'L'</code> and vice versa. (1 gal to 3.78541 L)
|
||||
testString: "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);
|
||||
}
|
||||
}
|
||||
"
|
||||
- text: I can convert <code>'lbs'</code> to <code>'kg'</code> and vice versa. (1 lbs to 0.453592 kg)
|
||||
testString: "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);
|
||||
}
|
||||
}
|
||||
"
|
||||
- text: I can convert <code>'mi'</code> to <code>'km'</code> and vice versa. (1 mi to 1.60934 km)
|
||||
testString: "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);
|
||||
}
|
||||
}
|
||||
"
|
||||
- text: All incoming units should be accepted in both upper and lower case, but should be returned in both the <code>initUnit</code> and <code>returnUnit</code> in lower case, except for liter, which should be represented as an uppercase <code>'L'</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: If my unit of measurement is invalid, returned will be <code>'invalid unit'</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}
|
||||
"
|
||||
- text: If my number is invalid, returned will be <code>'invalid number'</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: If both the unit and number are invalid, returned will be <code>'invalid number and unit'</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: I can use fractions, decimals or both in my parameter(ie. 5, 1/2, 2.5/6), but if nothing is provided it will default to 1.
|
||||
testString: "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);
|
||||
}
|
||||
}
|
||||
"
|
||||
- text: My return will consist of the <code>initNum</code>, <code>initUnit</code>, <code>returnNum</code>, <code>returnUnit</code>, and <code>string</code> spelling out units in the format <code>'{initNum} {initial_Units} converts to {returnNum} {return_Units}'</code> with the result rounded to 5 decimals.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: All 16 unit tests are complete and passing.
|
||||
testString: "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 ->/ig);
|
||||
});
|
||||
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);
|
||||
}
|
||||
}"
|
||||
- text: All 5 functional tests are complete and passing.
|
||||
testString: "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 ->/ig);
|
||||
});
|
||||
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);
|
||||
}
|
||||
}"
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(
|
||||
!/.*\/metric-imperial-converter\.freecodecamp\.rocks/.test(
|
||||
getUserInput('url')
|
||||
)
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
I can GET /api/convert with a single parameter containing an accepted number and unit and have it converted. (Hint: Split the input by looking for the index of the first character which will mark the start of the unit)
|
||||
|
||||
## Challenge Seed
|
||||
<section id='challengeSeed'>
|
||||
```js
|
||||
|
||||
</section>
|
||||
```
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
I can convert `'gal'` to `'L'` and vice versa. (1 gal to 3.78541 L)
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
I can convert `'lbs'` to `'kg'` and vice versa. (1 lbs to 0.453592 kg)
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
I can convert `'mi'` to `'km'` and vice versa. (1 mi to 1.60934 km)
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All incoming units should be accepted in both upper and lower case, but should be returned in both the `initUnit` and `returnUnit` in lower case, except for liter, which should be represented as an uppercase `'L'`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If my unit of measurement is invalid, returned will be `'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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If my number is invalid, returned will be `'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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If both the unit and number are invalid, returned will be `'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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
I can use fractions, decimals or both in my parameter(ie. 5, 1/2, 2.5/6), but if nothing is provided it will default to 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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
My return will consist of the `initNum`, `initUnit`, `returnNum`, `returnUnit`, and `string` spelling out units in the format `'{initNum} {initial_Units} converts to {returnNum} {return_Units}'` with the result rounded to 5 decimals.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 16 unit tests are complete and passing.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 5 functional tests are complete and passing.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
@@ -230,5 +282,3 @@ tests:
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@@ -5,177 +5,213 @@ challengeType: 4
|
||||
forumTopicId: 301571
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id='description'>
|
||||
Build a full stack JavaScript app that is functionally similar to this: <a href='https://personal-library.freecodecamp.rocks/' target='_blank'>https://personal-library.freecodecamp.rocks/</a>. Working on this project will involve you writing your code using one of the following methods:
|
||||
# --description--
|
||||
|
||||
- Clone <a href='https://github.com/freeCodeCamp/boilerplate-project-library' target='_blank'>this GitHub repo</a> and complete your project locally.
|
||||
- Use <a href='https://repl.it/github/freeCodeCamp/boilerplate-project-library' target='_blank'>our repl.it starter project</a>) to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
Build a full stack JavaScript app that is functionally similar to this: <https://personal-library.freecodecamp.rocks/>. Working on this project will involve you writing your code using one of the following methods:
|
||||
|
||||
- Clone [this GitHub repo](https://github.com/freeCodeCamp/boilerplate-project-library) and complete your project locally.
|
||||
- Use [our repl.it starter project](https://repl.it/github/freeCodeCamp/boilerplate-project-library)) to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
|
||||
When you are done, make sure a working demo of your project is hosted somewhere public. Then submit the URL to it in the `Solution Link` field. Optionally, also submit a link to your project's source code in the `GitHub Link` field.
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id='instructions'>
|
||||
# --instructions--
|
||||
|
||||
1. Add your MongoDB connection string to `.env` without quotes as `DB`
|
||||
Example: `DB=mongodb://admin:pass@1234.mlab.com:1234/fccpersonallib`
|
||||
2. In your `.env` file set `NODE_ENV` to `test`, without quotes
|
||||
3. You need to create all routes within `routes/api.js`
|
||||
4. You will create all functional tests in `tests/2_functional-tests.js`
|
||||
1. Add your MongoDB connection string to `.env` without quotes as `DB`
|
||||
Example: `DB=mongodb://admin:pass@1234.mlab.com:1234/fccpersonallib`
|
||||
2. In your `.env` file set `NODE_ENV` to `test`, without quotes
|
||||
3. You need to create all routes within `routes/api.js`
|
||||
4. You will create all functional tests in `tests/2_functional-tests.js`
|
||||
|
||||
</section>
|
||||
# --hints--
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
You can provide your own project, not the example URL.
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: You can provide your own project, not the example URL.
|
||||
testString: |
|
||||
getUserInput => {
|
||||
assert(!/.*\/personal-library\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
}
|
||||
- text: You can send a <b>POST</b> request to <code>/api/books</code> with <code>title</code> as part of the form data to add a book. The returned response will be an object with the <code>title</code> and a unique <code>_id</code> as keys. If <code>title</code> is not included in the request, the returned response should be the string <code>missing required field title</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: You can send a <b>GET</b> request to <code>/api/books</code> and receive a JSON response representing all the books. The JSON response will be an array of objects with each object (book) containing <code>title</code>, <code>_id</code>, and <code>commentcount</code> properties.
|
||||
testString: "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' });
|
||||
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);
|
||||
}
|
||||
}"
|
||||
- text: You can send a <b>GET</b> request to <code>/api/books/{_id}</code> to retrieve a single object of a book containing the properties <code>title</code>, <code>_id</code>, and a <code>comments</code> array (empty array if no comments present). If no book is found, return the string <code>no book exists</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: You can send a <b>POST</b> request containing <code>comment</code> as the form body data to <code>/api/books/{_id}</code> to add a comment to a book. The returned response will be the books object similar to <b>GET</b> <code>/api/books/{_id}</code> request in an earlier test. If <code>comment</code> is not included in the request, return the string <code>missing required field comment</code>`. If no book is found, return the string <code>no book exists</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: You can send a <b>DELETE</b> request to <code>/api/books/{_id}</code> to delete a book from the collection. The returned response will be the string <code>delete successful</code> if successful. If no book is found, return the string <code>no book exists</code>.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: You can send a <b>DELETE</b> request to <code>/api/books</code> to delete all books in the database. The returned response will be the string <code>'complete delete successful</code> if successful.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: All 10 functional tests required are complete and passing.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
```js
|
||||
(getUserInput) => {
|
||||
assert(
|
||||
!/.*\/personal-library\.freecodecamp\.rocks/.test(getUserInput('url'))
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
You can send a <b>POST</b> request to `/api/books` with `title` as part of the form data to add a book. The returned response will be an object with the `title` and a unique `_id` as keys. If `title` is not included in the request, the returned response should be the string `missing required field title`.
|
||||
|
||||
## Challenge Seed
|
||||
<section id='challengeSeed'>
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
You can send a <b>GET</b> request to `/api/books` and receive a JSON response representing all the books. The JSON response will be an array of objects with each object (book) containing `title`, `_id`, and `commentcount` properties.
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
```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' });
|
||||
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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>GET</b> request to `/api/books/{_id}` to retrieve a single object of a book containing the properties `title`, `_id`, and a `comments` array (empty array if no comments present). If no book is found, return the string `no book exists`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>POST</b> request containing `comment` as the form body data to `/api/books/{_id}` to add a comment to a book. The returned response will be the books object similar to <b>GET</b> `/api/books/{_id}` request in an earlier test. If `comment` is not included in the request, return the string \`missing required field comment\`\`. If no book is found, return the string `no book exists`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>DELETE</b> request to `/api/books/{_id}` to delete a book from the collection. The returned response will be the string `delete successful` if successful. If no book is found, return the string `no book exists`.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
You can send a <b>DELETE</b> request to `/api/books` to delete all books in the database. The returned response will be the string `'complete delete successful` if successful.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 10 functional tests required are complete and passing.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
@@ -184,5 +220,3 @@ tests:
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@@ -4,308 +4,349 @@ title: Sudoku Solver
|
||||
challengeType: 4
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id='description'>
|
||||
Build a full stack JavaScript app that is functionally similar to this: <a href="https://sudoku-solver.freecodecamp.rocks/" target="_blank">https://sudoku-solver.freecodecamp.rocks/</a>. Working on this project will involve you writing your code using one of the following methods:
|
||||
# --description--
|
||||
|
||||
- Clone <a href='https://github.com/freecodecamp/boilerplate-project-sudoku-solver' target='_blank'>this GitHub repo</a> and complete your project locally.
|
||||
- Use <a href='https://repl.it/github/freeCodeCamp/boilerplate-project-sudoku-solver' target='_blank'>our repl.it starter project</a> to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
Build a full stack JavaScript app that is functionally similar to this: <https://sudoku-solver.freecodecamp.rocks/>. Working on this project will involve you writing your code using one of the following methods:
|
||||
|
||||
- Clone [this GitHub repo](https://github.com/freecodecamp/boilerplate-project-sudoku-solver) and complete your project locally.
|
||||
- Use [our repl.it starter project](https://repl.it/github/freeCodeCamp/boilerplate-project-sudoku-solver) to complete your project.
|
||||
- Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo.
|
||||
|
||||
When you are done, make sure a working demo of your project is hosted somewhere public. Then submit the URL to it in the `Solution Link` field. Optionally, also submit a link to your project's source code in the `GitHub Link` field.
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id='instructions'>
|
||||
# --instructions--
|
||||
|
||||
- All puzzle logic can go into `/controllers/sudoku-solver.js`
|
||||
- All routing logic can go into `/routes/api.js`
|
||||
- See the `puzzle-strings.js` file in `/controllers` for some sample puzzles your application should solve
|
||||
- To run the challenge tests on this page, set `NODE_ENV` to `test` without quotes in the `.env` file
|
||||
- To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
|
||||
- All puzzle logic can go into `/controllers/sudoku-solver.js`
|
||||
- All routing logic can go into `/routes/api.js`
|
||||
- See the `puzzle-strings.js` file in `/controllers` for some sample puzzles your application should solve
|
||||
- To run the challenge tests on this page, set `NODE_ENV` to `test` without quotes in the `.env` file
|
||||
- To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
|
||||
|
||||
Write the following tests in `tests/1_unit-tests.js`:
|
||||
|
||||
- Logic handles a valid puzzle string of 81 characters
|
||||
- Logic handles a puzzle string with invalid characters (not 1-9 or `.`)
|
||||
- Logic handles a puzzle string that is not 81 characters in length
|
||||
- Logic handles a valid row placement
|
||||
- Logic handles an invalid row placement
|
||||
- Logic handles a valid column placement
|
||||
- Logic handles an invalid column placement
|
||||
- Logic handles a valid region (3x3 grid) placement
|
||||
- Logic handles an invalid region (3x3 grid) placement
|
||||
- Valid puzzle strings pass the solver
|
||||
- Invalid puzzle strings fail the solver
|
||||
- Solver returns the the expected solution for an incomplete puzzzle
|
||||
- Logic handles a valid puzzle string of 81 characters
|
||||
- Logic handles a puzzle string with invalid characters (not 1-9 or `.`)
|
||||
- Logic handles a puzzle string that is not 81 characters in length
|
||||
- Logic handles a valid row placement
|
||||
- Logic handles an invalid row placement
|
||||
- Logic handles a valid column placement
|
||||
- Logic handles an invalid column placement
|
||||
- Logic handles a valid region (3x3 grid) placement
|
||||
- Logic handles an invalid region (3x3 grid) placement
|
||||
- Valid puzzle strings pass the solver
|
||||
- Invalid puzzle strings fail the solver
|
||||
- Solver returns the the expected solution for an incomplete puzzzle
|
||||
|
||||
Write the following tests in `tests/2_functional-tests.js`
|
||||
|
||||
- Solve a puzzle with valid puzzle string: POST request to `/api/solve`
|
||||
- Solve a puzzle with missing puzzle string: POST request to `/api/solve`
|
||||
- Solve a puzzle with invalid characters: POST request to `/api/solve`
|
||||
- Solve a puzzle with incorrect length: POST request to `/api/solve`
|
||||
- Solve a puzzle that cannot be solved: POST request to `/api/solve`
|
||||
- Check a puzzle placement with all fields: POST request to `/api/check`
|
||||
- Check a puzzle placement with single placement conflict: POST request to `/api/check`
|
||||
- Check a puzzle placement with multiple placement conflicts: POST request to `/api/check`
|
||||
- Check a puzzle placement with all placement conflicts: POST request to `/api/check`
|
||||
- Check a puzzle placement with missing required fields: POST request to `/api/check`
|
||||
- Check a puzzle placement with invalid characters: POST request to `/api/check`
|
||||
- Check a puzzle placement with incorrect length: POST request to `/api/check`
|
||||
- Check a puzzle placement with invalid placement coordinate: POST request to `/api/check`
|
||||
- Check a puzzle placement with invalid placement value: POST request to `/api/check`
|
||||
- Solve a puzzle with valid puzzle string: POST request to `/api/solve`
|
||||
- Solve a puzzle with missing puzzle string: POST request to `/api/solve`
|
||||
- Solve a puzzle with invalid characters: POST request to `/api/solve`
|
||||
- Solve a puzzle with incorrect length: POST request to `/api/solve`
|
||||
- Solve a puzzle that cannot be solved: POST request to `/api/solve`
|
||||
- Check a puzzle placement with all fields: POST request to `/api/check`
|
||||
- Check a puzzle placement with single placement conflict: POST request to `/api/check`
|
||||
- Check a puzzle placement with multiple placement conflicts: POST request to `/api/check`
|
||||
- Check a puzzle placement with all placement conflicts: POST request to `/api/check`
|
||||
- Check a puzzle placement with missing required fields: POST request to `/api/check`
|
||||
- Check a puzzle placement with invalid characters: POST request to `/api/check`
|
||||
- Check a puzzle placement with incorrect length: POST request to `/api/check`
|
||||
- Check a puzzle placement with invalid placement coordinate: POST request to `/api/check`
|
||||
- Check a puzzle placement with invalid placement value: POST request to `/api/check`
|
||||
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: You should provide your own project, not the example URL.
|
||||
testString: |
|
||||
getUserInput => {
|
||||
const url = getUserInput('url');
|
||||
assert(!/.*\/sudoku-solver\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
}
|
||||
- text: You can `POST` `/api/solve` with form data containing `puzzle` which will be a string containing a combination of numbers (1-9) and periods `.` to represent empty spaces. The returned object will contain a `solution` property with the solved puzzle.
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the object submitted to `/api/solve` is missing `puzzle`, the returned value will be `{ error: 'Required field missing' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the puzzle submitted to `/api/solve` contains values which are not numbers or periods, the returned value will be `{ error: 'Invalid characters in puzzle' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the puzzle submitted to `/api/solve` is greater or less than 81 characters, the returned value will be `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the puzzle submitted to `/api/solve` is invalid or cannot be solved, the returned value will be `{ error: 'Puzzle cannot be solved' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: You can `POST` to `/api/check` an object containing `puzzle`, `coordinate`, and `value` where the `coordinate` is the letter A-I indicating the row, followed by a number 1-9 indicating the column, and `value` is a number from 1-9.
|
||||
testString: "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);
|
||||
}"
|
||||
- text: The return value from the `POST` to `/api/check` will be an object containing a `valid` property, which is `true` if the number may be placed at the provided coordinate and `false` if the number may not. If false, the returned object will also contain a `conflict` property which is an array containing the strings `"row"`, `"column"`, and/or `"region"` depending on which makes the placement invalid.
|
||||
testString: "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');
|
||||
}"
|
||||
- text: If the puzzle submitted to `/api/check` contains values which are not numbers or periods, the returned value will be `{ error: 'Invalid characters in puzzle' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the puzzle submitted to `/api/check` is greater or less than 81 characters, the returned value will be `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the object submitted to `/api/check` is missing `puzzle`, `coordinate` or `value`, the returned value will be `{ error: Required field(s) missing }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the coordinate submitted to `api/check` does not point to an existing grid cell, the returned value will be `{ error: 'Invalid coordinate'}`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: If the `value` submitted to `/api/check` is not a number between 1 and 9, the returned values will be `{ error: 'Invalid value' }`
|
||||
testString: "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);
|
||||
}"
|
||||
- text: All 12 unit tests are complete and passing. See `/tests/1_unit-tests.js` for the expected behavior you should write tests for.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
- text: All 14 functional tests are complete and passing. See `/tests/2_functional-tests.js` for the functionality you should write tests for.
|
||||
testString: "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);
|
||||
}
|
||||
}"
|
||||
# --hints--
|
||||
|
||||
You should provide your own project, not the example URL.
|
||||
|
||||
```js
|
||||
(getUserInput) => {
|
||||
const url = getUserInput('url');
|
||||
assert(!/.*\/sudoku-solver\.freecodecamp\.rocks/.test(getUserInput('url')));
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
You can `POST` `/api/solve` with form data containing `puzzle` which will be a string containing a combination of numbers (1-9) and periods `.` to represent empty spaces. The returned object will contain a `solution` property with the solved puzzle.
|
||||
|
||||
## Challenge Seed
|
||||
<section id='challengeSeed'>
|
||||
```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);
|
||||
};
|
||||
```
|
||||
|
||||
</section>
|
||||
If the object submitted to `/api/solve` is missing `puzzle`, the returned value will be `{ error: 'Required field missing' }`
|
||||
|
||||
## Solution
|
||||
<section id='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 = '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);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/solve` contains values which are not numbers or periods, the returned value will be `{ error: 'Invalid characters in puzzle' }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/solve` is greater or less than 81 characters, the returned value will be `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/solve` is invalid or cannot be solved, the returned value will be `{ error: 'Puzzle cannot be solved' }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
You can `POST` to `/api/check` an object containing `puzzle`, `coordinate`, and `value` where the `coordinate` is the letter A-I indicating the row, followed by a number 1-9 indicating the column, and `value` is a number from 1-9.
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
The return value from the `POST` to `/api/check` will be an object containing a `valid` property, which is `true` if the number may be placed at the provided coordinate and `false` if the number may not. If false, the returned object will also contain a `conflict` property which is an array containing the strings `"row"`, `"column"`, and/or `"region"` depending on which makes the placement invalid.
|
||||
|
||||
```js
|
||||
async (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');
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/check` contains values which are not numbers or periods, the returned value will be `{ error: 'Invalid characters in puzzle' }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
If the puzzle submitted to `/api/check` is greater or less than 81 characters, the returned value will be `{ error: 'Expected puzzle to be 81 characters long' }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
If the object submitted to `/api/check` is missing `puzzle`, `coordinate` or `value`, the returned value will be `{ error: Required field(s) missing }`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
If the coordinate submitted to `api/check` does not point to an existing grid cell, the returned value will be `{ error: 'Invalid coordinate'}`
|
||||
|
||||
```js
|
||||
async (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);
|
||||
};
|
||||
```
|
||||
|
||||
If the `value` submitted to `/api/check` is not a number between 1 and 9, the returned values will be `{ 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);
|
||||
};
|
||||
```
|
||||
|
||||
All 12 unit tests are complete and passing. See `/tests/1_unit-tests.js` for the expected behavior you should write tests for.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
All 14 functional tests are complete and passing. See `/tests/2_functional-tests.js` for the functionality you should write tests for.
|
||||
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
# --solutions--
|
||||
|
||||
```js
|
||||
/**
|
||||
@@ -314,5 +355,3 @@ tests:
|
||||
Please check our contributing guidelines to learn more.
|
||||
*/
|
||||
```
|
||||
|
||||
</section>
|
||||
|
Reference in New Issue
Block a user