Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add tests for Sudoku Solver #39816

Merged
merged 4 commits into from Nov 7, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -8,52 +8,260 @@ challengeType: 4
<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 on Repl.it on our starter project. After completing this project you can copy your public Repl.it URL (to the homepage of your app) into this screen to test it! Optionally you may choose to write your project on another platform but it must be publicly visible for our testing.
- 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.

Start this project on Repl.it using <a href="https://repl.it/github/freeCodeCamp/boilerplate-project-sudoku-solver">this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-project-sudoku-solver/'>this repository</a> on GitHub! If you use Repl.it, remember to save the link to your project somewhere safe!
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'>

- 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"

</section>

## Tests
<section id='tests'>

```yml
tests:
- text: I can provide my own project, not the example URL.
- 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: I can enter a sudoku puzzle by filling in the text area with either a number or period (.) to represent an empty cell.
testString: ''
- text: When a valid number is entered in the text area, the same number is applied to the correct cell of the sudoku grid.
testString: ''
- text: I can enter a sudoku puzzle by adding numbers directly to the sudoku grid.
testString: ''
- text: When a valid number is entered in the sudoku grid, the same number appears in the correct position in the text area.
testString: ''
- text: The text area should only update the corresponding sudoku grid cell when a whole number between 1 and 9 is entered.
testString: ''
- text: The sudoku grid should only update the puzzle string in the text area when a whole number between 1 and 9 is entered into a cell.
testString: ''
- text: I can solve an incomplete puzzle by clicking the "Solve" button. When a solution is found, the sudoku grid and text area are automatically populated with the correct numbers for each cell in the grid or position in the text area.
testString: ''
- text: This sudoku solver is not expected to be able to solve every incomplete puzzle. See <code>/public/puzzle-strings.js</code> for a list of puzzle strings it should be able to solve along with their solutions.
testString: ''
- text: |
If the puzzle is not 81 numbers or periods long, append the message "Error: Expected puzzle to be 81 characters long." to the <code>error-msg</code> <code>div</code> so the text appears in red.
testString: ''
- text: I can clear the text area and sudoku grid by clicking the "Clear" button.
testString: ''
- text: All 6 unit tests are complete and passing. See <code>/tests/1_unit-tests.js</code> for the expected behavior you should write tests for.
testString: ''
- text: All 4 functional tests are complete and passing. See <code>/tests/2_functional-tests.js</code> for the functionality you should write tests for.
testString: ''
- 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&#58; '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);
}
}"


```

Expand Down