diff --git a/2-write-test-case/1-function-design/README.md b/2-write-test-case/1-function-design/README.md new file mode 100644 index 00000000..370528c8 --- /dev/null +++ b/2-write-test-case/1-function-design/README.md @@ -0,0 +1,120 @@ +# Function Design + +Solving code challenges doesn't need to be like walking in the dark. +We can't provide you with a perfect recipe to solve every problem, but there are some steps you can take to learn a structured workflow. +The goal of the examples and exercises in this folder is to help you explore a _function design workflow_ that you can master +and apply in your own projects. + +This README is a big-picture walk through this workflow, the `./examples` directory will go into more detail. +The format of these exercises will take a little time to get used to, but that's all part of the learning! + +- [The Workflow](#the-workflow) +- Examples : contains a .js file for each step in the workflow for an example problem. +- Exercises : contains only one .js file per problem. You will solve it step by step in the same file. +- [References](#references) + +--- + +## The Workflow + +0. Starter Code +1. Write Tests +1. Find a Strategy +1. Design Cycles +1. More Strategies + +Your first instinct may be to just start writing a function hoping for the best. +This workflow will help you work against that instinct and build new ones. +You will first think about + +1. how to describe the function's behavior (JSDoc description) +2. what types go in and out of your function (JSDoc @params & @returns) +3. how the function behaves (test cases) +4. how you can approach the problem (a strategy) +5. and finally ... what code to write (implementation) + +One of the trickiest (but most important!) parts of this workflow will be writing tests for the challenge _before_ writing your solution. +Writing tests is not easy to learn but is worth the effort. +Writing tests will force you to understand the challenge from the outside in and will not let you get away with a half-understanding. +Building this understanding before writing your function will make the problem solving and debugging easier. + +### 0. Starter Code + +- ./examples/repeat-a-string/0-starter-code.test.js + +Each challenge will come with some starter code that includes: + +- a JSDoc describing the challenge +- test cases (sometimes empty, sometimes started) +- a secret solution that you can use to write tests + +--- + +### 1. Write Tests + +> some exercises will come with starter tests, that's just a start. add more! + +- ./examples/repeat-a-string/1-write-tests.test.js + +Don't rush this step! Taking a little extra time to think about your function's behavior and +to consider [_edge cases_](https://www.geeksforgeeks.org/dont-forget-edge-cases/) will help in more than one way: + +1. You will better understand the problem, so solving it will be easier. +2. You can have more confidence in your solution since the tests are more thorough. +3. Debugging will be easier when you have a clear idea of the expected results. + +In these exercises you will always write your tests by calling `solution()`, this is to use the variable declared in the testing loop. If you call a different function than `solution`, you will not be able to run your tests against different solutions. + +To help you write good tests, each exercise has a compressed, complicated, and hard-to-read solution included in the file (don't cheat! honor code.). +You will use this `secretSolution` to test your tests. There's nothing more frustrating than trying to use broken tests. +If your tests are wrong, it becomes nearly impossible to debug your solution! +So be sure to run your tests against the `secretSolution` before testing your own solution. +If a test fails the `secretSolution` then you know the test is bad, adjust it until it passes before moving on. + +Finally, don't worry yourself trying to write perfect tests for every possible case. You can always add more tests as you design your solution. + +--- + +### 2. First Strategy + +- ./examples/repeat-a-string/2-first-strategy.test.js + +Think of a first strategy for solving this challenge. You might write is as numbered steps, pseudo-code or a short paragraph, whatever helps you most. + +Anything goes! This is a chance to think big and imagine new ways you could approach this problem without worrying about how you will code it. You'll explore different implementations in the next step. + +--- + +### 3. Design Cycles + +- ./examples/repeat-a-string/3-design-cycles.test.js + +Try to write as many implementations as possible for your strategy. Keep all of the, especially the ones that don't work! In this step it's _very_ important that you keep all of your experiments and take notes on what you tried in each one. If you take good notes you can always come back to them when you have a similar problem in a project. + +Be sure to run your tests after every small change you make in a function. And don't forget to double check which solutions are commented ;) + +--- + +### 4. More Strategies + +- ./examples/repeat-a-string/4-more-strategies.test.js + +So you've written a few solutions with your first strategy? Pick a new strategy and try solving it again! + +You can often learn more from approaching the same problem in new ways than from always starting a new problem. Being very familiar with what you are trying to achieve frees your mind to be creative. + +--- + +## References + +- Similar Online Resources + - Looking for more explanation and a softer start? take a look through these: + - [The Learning Collective JS](http://tlcjs.org/index.html) - chapters 1, 2, 3 + - [Systematic Function Design in JS](https://wavded.com/post/htdp-functions/) - short article +- Good Code + - [Improve Code Readability](https://dev.to/briwa/simple-ways-to-improve-code-readability-2n8c) + - [Comments, Clean Code, JS](https://medium.com/better-programming/javascript-clean-code-comments-c926d5aae2cb) + - [Comments: Best Practices](https://www.elegantthemes.com/blog/wordpress/how-to-comment-your-code-like-a-pro-best-practices-and-good-habits) +- [How to Design Programs](https://htdp.org/2019-02-24/part_preface.html) + - This gold-standard textbook is written for the Racket programming language + - The design workflow you will learn in `behavior-strategy-implementation` is inspired by HTDP then adapted for JS and Study Lenses diff --git a/2-write-test-case/1-function-design/examples/repeat-a-string/0-starter-code.test.js b/2-write-test-case/1-function-design/examples/repeat-a-string/0-starter-code.test.js new file mode 100644 index 00000000..4aae1595 --- /dev/null +++ b/2-write-test-case/1-function-design/examples/repeat-a-string/0-starter-code.test.js @@ -0,0 +1,43 @@ +// #todo + +'use strict'; + +/* 0. Starter Code + + Each exercises will have this format: + - a JSDoc describing the behavior + - an empty space where you'll write your solutions + - a for-of loop with test cases inside + - a secret solution you can use to write tests + +*/ + +// =============== JSDoc description of the challenge =============== + +/** + * Repeats a string a specific number of times. + * + * @param {string} [text=''] - The string to repeat. defaults to empty string. + * @param {number} [repetitions=1] - How many times to repeat. defaults to 1. + * Repetitions must be greater than zero, and must be an integer. + * @return {string} The text repeated as many times as repetitions. + */ + +// =============== your solutions will go here =============== + +// =============== a for-of loop to control which solution(s) are tested =============== + +for (const solution of [secretSolution]) { + // =============== test cases for this challenge =============== + + describe(solution.name + ': _', () => { + describe('_', () => { + it('_', () => {}); + }); + }); +} + +// =============== a minified solution you can use to test your test cases =============== + +// prettier-ignore +function secretSolution(a = "", b = 1) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("number" != typeof b) { throw new TypeError("repetitions is not a number"); } if (0 > b) { throw new RangeError("repetitions is less than zero"); } if (!Number.isInteger(b)) { throw new RangeError("repetitions is not an integer"); } return Array(b).fill(a).join(""); } diff --git a/2-write-test-case/1-function-design/examples/repeat-a-string/1-write-tests.test.js b/2-write-test-case/1-function-design/examples/repeat-a-string/1-write-tests.test.js new file mode 100644 index 00000000..952df5c4 --- /dev/null +++ b/2-write-test-case/1-function-design/examples/repeat-a-string/1-write-tests.test.js @@ -0,0 +1,82 @@ +// #todo + +'use strict'; + +/* 1. Write Test Cases + + Write as many test cases as you can to understand the challenge + try thinking of edge cases! + + The secret solution is correct. if your tests fail, fix your tests ;) + + After this step you will have a full suite of tests to check your own solutions. + +*/ + +// =============== JSDoc description of the challenge =============== + +/** + * repeats a string a specific number of times + * @param {string} [text=''] - the string to repeat. defaults to empty string + * @param {number} [repetitions=1] - how many times to repeat. defaults to 1 + * repetitions must be greater than zero, and must be an integer + * @return {string} the text repeated as many times as repetitions + */ + +// =============== your solutions will go here =============== + +// =============== a for-of loop to control which solution(s) are tested =============== + +for (const solution of [secretSolution]) { + // =============== test cases for this challenge =============== + + describe(solution.name + ': repeating a string:', () => { + describe('default parameters', () => { + it('repeat once if no repetitions is passed', () => { + expect(solution('asdf')).toEqual('asdf'); + }); + it('expect an empty string if no arguments are passed', () => { + expect(solution()).toEqual(''); + }); + }); + describe('an empty string', () => { + it('repeat an empty string 0 times -> ""', () => { + expect(solution('', 0)).toEqual(''); + }); + it('repeat an empty string 10 times -> ""', () => { + expect(solution('', 10)).toEqual(''); + }); + it('repeat an empty string 100 times -> ""', () => { + expect(solution('', 100)).toEqual(''); + }); + }); + describe('zero repetitions', () => { + it('repeat "asdf" 0 times -> ""', () => { + expect(solution('asdf', 0)).toEqual(''); + }); + it('repeat "tommywalk" 0 times -> ""', () => { + expect(solution('tommywalk', 0)).toEqual(''); + }); + }); + describe('standard use cases', () => { + it('repeating a longer phrase 3 times', () => { + expect(solution('go to school', 3)).toEqual( + 'go to schoolgo to schoolgo to school' + ); + }); + it('repeating a phrase with punctuation', () => { + expect(solution('"Go!", said Dr. Seuss?', 2)).toEqual( + '"Go!", said Dr. Seuss?"Go!", said Dr. Seuss?' + ); + }); + it('a string with special characters can be repeated', () => { + expect(solution('\\ \n \t s', 2)).toEqual('\\ \n \t s\\ \n \t s'); + }); + }); + }); +} + +// =============== a minified solution you can use to test your test cases =============== + +// prettier-ignore +function secretSolution(a = "", b = 1) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("number" != typeof b) { throw new TypeError("repetitions is not a number"); } if (0 > b) { throw new RangeError("repetitions is less than zero"); } if (!Number.isInteger(b)) { throw new RangeError("repetitions is not an integer"); } return Array(b).fill(a).join(""); } diff --git a/2-write-test-case/1-function-design/examples/repeat-a-string/2-first-strategy.test.js b/2-write-test-case/1-function-design/examples/repeat-a-string/2-first-strategy.test.js new file mode 100644 index 00000000..723a7bb7 --- /dev/null +++ b/2-write-test-case/1-function-design/examples/repeat-a-string/2-first-strategy.test.js @@ -0,0 +1,95 @@ +// #todo + +'use strict'; + +/* 2. First Strategy + + Think of a strategy for solving this challenge, it doesn't need to be perfect! + + write out your strategy in a block comment, as pseudocode or normal sentences + + you will try finding a few implementations for your strategy. + +*/ + +// =============== JSDoc description of the challenge =============== + +/** + * repeats a string a specific number of times + * @param {string} [text=''] - the string to repeat. defaults to empty string + * @param {number} [repetitions=1] - how many times to repeat. defaults to 1 + * repetitions must be greater than zero, and must be an integer + * @return {string} the text repeated as many times as repetitions + */ + +// =============== your solutions will go here =============== + +/* -- iteration: append the string to itself once for each repetition -- + + 1. create a new empty string to store the accumulated result + 2. iterate from zero the the number of repetitions + a. add the text to the accumulator one more time + return: the accumulated result + +*/ + +const forLoopTry1 = (text = '', repetitions = 1) => {}; + +// =============== a for-of loop to control which solution(s) are tested =============== + +for (const solution of [ + // secretSolution, // <--- comment out the secretSolution + forLoopTry1, // <--- write your function's name here. and you're ready to test! +]) { + // =============== test cases for this challenge =============== + + describe(solution.name + ': repeating a string:', () => { + describe('default parameters', () => { + it('repeat once if no repetitions is passed', () => { + expect(solution('asdf')).toEqual('asdf'); + }); + it('expect an empty string if no arguments are passed', () => { + expect(solution()).toEqual(''); + }); + }); + describe('an empty string', () => { + it('repeat an empty string 0 times -> ""', () => { + expect(solution('', 0)).toEqual(''); + }); + it('repeat an empty string 10 times -> ""', () => { + expect(solution('', 10)).toEqual(''); + }); + it('repeat an empty string 100 times -> ""', () => { + expect(solution('', 100)).toEqual(''); + }); + }); + describe('zero repetitions', () => { + it('repeat "asdf" 0 times -> ""', () => { + expect(solution('asdf', 0)).toEqual(''); + }); + it('repeat "tommywalk" 0 times -> ""', () => { + expect(solution('tommywalk', 0)).toEqual(''); + }); + }); + describe('standard use cases', () => { + it('repeating a longer phrase 3 times', () => { + expect(solution('go to school', 3)).toEqual( + 'go to schoolgo to schoolgo to school' + ); + }); + it('repeating a phrase with punctuation', () => { + expect(solution('"Go!", said Dr. Seuss?', 2)).toEqual( + '"Go!", said Dr. Seuss?"Go!", said Dr. Seuss?' + ); + }); + it('a string with special characters can be repeated', () => { + expect(solution('\\ \n \t s', 2)).toEqual('\\ \n \t s\\ \n \t s'); + }); + }); + }); +} + +// =============== a minified solution you can use to test your test cases =============== + +// prettier-ignore +function secretSolution(a = "", b = 1) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("number" != typeof b) { throw new TypeError("repetitions is not a number"); } if (0 > b) { throw new RangeError("repetitions is less than zero"); } if (!Number.isInteger(b)) { throw new RangeError("repetitions is not an integer"); } return Array(b).fill(a).join(""); } diff --git a/2-write-test-case/1-function-design/examples/repeat-a-string/3-design-cycles.test.js b/2-write-test-case/1-function-design/examples/repeat-a-string/3-design-cycles.test.js new file mode 100644 index 00000000..15fb5dd5 --- /dev/null +++ b/2-write-test-case/1-function-design/examples/repeat-a-string/3-design-cycles.test.js @@ -0,0 +1,157 @@ +// #todo + +'use strict'; + +/* 3. Design Cycles + + try to implement your strategy as many ways as possible + this is a chance to explore all the language features of JS + + to get the most out of these exercises remember: + there are no bad ideas + take lots of notes + fail a lot of tests! + +*/ + +// =============== JSDoc description of the challenge =============== + +/** + * Repeats a string a specific number of times. + * + * @param {string} [text=''] - The string to repeat. defaults to empty string. + * @param {number} [repetitions=1] - How many times to repeat. defaults to 1. + * Repetitions must be greater than zero, and must be an integer. + * @return {string} The text repeated as many times as repetitions. + */ + +// =============== your solutions will go here =============== + +/* -- iteration: append the string to itself once for each repetition -- + + 1. create a new empty string to store the accumulated result + 2. iterate from zero the the number of repetitions + a. add the text to the accumulator one more time + return: the accumulated result + +*/ + +/* failed all the tests - always returned undefined + repeated is declared and returnd, but never modified +*/ +const forLoopTry1 = (text = '', repetitions = 1) => { + let repeated; + for (let i = 0; i < repetitions; i++) { + text += text; + } + return repeated; +}; + +/* failed all the tests + repeated was initialized as undefined, so adding the text makes `undefinedtextext...` +*/ +const forLoopTry2 = (text = '', repetitions = 1) => { + // debugger; + let repeated; + for (let i = 0; i < repetitions; i++) { + repeated += text; + } + return repeated; +}; + +// success! initializing repeated to empty string worked +const forLoopTry3 = (text = '', repetitions = 1) => { + let repeated = ''; + for (let i = 0; i < repetitions; i++) { + repeated += text; + } + return repeated; +}; + +// can I do this just by modifying the parameter? +// i did that by accident in my first attempt, but it could work +/* nope: the text is repeated way too many times + 'asdf' -> 'asdfasdf' -> 'asdfasdfasdfasdf' + it doubles with each time instead of being appended just once +*/ +const withoutExtraVariable = (text = '', repetitions = 1) => { + // debugger; + for (let i = 0; i < repetitions; i++) { + text += text; + } + return text; +}; + +// refactoring this strategy to a while loop +const whileLoop = (text = '', repetitions = 1) => { + let repeated = ''; + let i = 0; + while (i < repetitions) { + repeated += text; + i++; + } + return repeated; +}; + +// =============== a for-of loop to control which solution(s) are tested =============== + +for (const solution of [ + // secretSolution, + // forLoopTry1, + // forLoopTry2, + forLoopTry3, // success! + // whileLoop, // success again + // withoutExtraVariable, +]) { + // =============== test cases for this challenge =============== + + describe(solution.name + ': repeating a string:', () => { + describe('default parameters', () => { + it('repeat once if no repetitions is passed', () => { + expect(solution('asdf')).toEqual('asdf'); + }); + it('expect an empty string if no arguments are passed', () => { + expect(solution()).toEqual(''); + }); + }); + describe('an empty string', () => { + it('repeat an empty string 0 times -> ""', () => { + expect(solution('', 0)).toEqual(''); + }); + it('repeat an empty string 10 times -> ""', () => { + expect(solution('', 10)).toEqual(''); + }); + it('repeat an empty string 100 times -> ""', () => { + expect(solution('', 100)).toEqual(''); + }); + }); + describe('zero repetitions', () => { + it('repeat "asdf" 0 times -> ""', () => { + expect(solution('asdf', 0)).toEqual(''); + }); + it('repeat "tommywalk" 0 times -> ""', () => { + expect(solution('tommywalk', 0)).toEqual(''); + }); + }); + describe('standard use cases', () => { + it('repeating a longer phrase 3 times', () => { + expect(solution('go to school', 3)).toEqual( + 'go to schoolgo to schoolgo to school' + ); + }); + it('repeating a phrase with punctuation', () => { + expect(solution('"Go!", said Dr. Seuss?', 2)).toEqual( + '"Go!", said Dr. Seuss?"Go!", said Dr. Seuss?' + ); + }); + it('a string with special characters can be repeated', () => { + expect(solution('\\ \n \t s', 2)).toEqual('\\ \n \t s\\ \n \t s'); + }); + }); + }); +} + +// =============== a minified solution you can use to test your test cases =============== + +// prettier-ignore +function secretSolution(a = "", b = 1) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("number" != typeof b) { throw new TypeError("repetitions is not a number"); } if (0 > b) { throw new RangeError("repetitions is less than zero"); } if (!Number.isInteger(b)) { throw new RangeError("repetitions is not an integer"); } return Array(b).fill(a).join(""); } diff --git a/2-write-test-case/1-function-design/examples/repeat-a-string/4-more-strategies.test.js b/2-write-test-case/1-function-design/examples/repeat-a-string/4-more-strategies.test.js new file mode 100644 index 00000000..536116c5 --- /dev/null +++ b/2-write-test-case/1-function-design/examples/repeat-a-string/4-more-strategies.test.js @@ -0,0 +1,294 @@ +// #todo + +'use strict'; + +/* 4. More Strategies! + + After solving the challenge a few times with one strategy, try new strategies + + there is no real "end" to these exercises + you could keep solving the same challenge in different ways for days + move on to the next exercise when you feel comfortable with 2-3 strategiesdis + +*/ + +// =============== JSDoc description of the challenge =============== + +/** + * Repeats a string a specific number of times. + * + * @param {string} [text=''] - The string to repeat. defaults to empty string. + * @param {number} [repetitions=1] - How many times to repeat. defaults to 1. + * Repetitions must be greater than zero, and must be an integer. + * @return {string} The text repeated as many times as repetitions. + */ + +// =============== your solutions will go here =============== + +/* -- iteration: append the string to itself once for each repetition -- + + 1. create a new empty string to store the accumulated result + 2. iterate from zero the the number of repetitions + a. add the text to the accumulator one more time + return: the accumulated result + +*/ + +/* failed all the tests - always returned undefined + repeated is declared and returnd, but never modified +*/ +const forLoopTry1 = (text = '', repetitions = 1) => { + let repeated; + for (let i = 0; i < repetitions; i++) { + text += text; + } + return repeated; +}; + +/* failed all the tests + repeated was initialized as undefined, so adding the text makes `undefinedtextext...` +*/ +const forLoopTry2 = (text = '', repetitions = 1) => { + // debugger; + let repeated; + for (let i = 0; i < repetitions; i++) { + repeated += text; + } + return repeated; +}; + +// success! initializing repeated to empty string worked +const forLoopTry3 = (text = '', repetitions = 1) => { + let repeated = ''; + for (let i = 0; i < repetitions; i++) { + repeated += text; + } + return repeated; +}; + +// can I do this just by modifying the parameter? +// i did that by accident in my first attempt, but it could work +/* nope: the text is repeated way too many times + 'asdf' -> 'asdfasdf' -> 'asdfasdfasdfasdf' + it doubles with each time instead of being appended just once +*/ +const withoutExtraVariable = (text = '', repetitions = 1) => { + // debugger; + for (let i = 0; i < repetitions; i++) { + text += text; + } + return text; +}; + +/* --- iterate while the final string length is too short --- + + 1. declare new empty string + 2. calculate how long the final string should be + 3. iterate while new string is too short + a. add the text to the accumulator + return: the new string + +*/ + +const iterateWhileTooShort = (text = '', repetitions = 1) => { + let finalString = ''; + const finalLength = text.length * repetitions; + while (finalString.length < finalLength) { + finalString += text; + } + return finalString; +}; + +/* -- recursion -- + + repeatString(text, repetitions) + base-case: repetitions is 0 + return: the text + recursive case: repetitions is more than 0 + 1. add the text to the text + 2. subtract one from repetitions + return: repeatString(combinedText, smallerRepetitions) + +*/ + +/* not at all + infinite recursion for repetitions === 0 + no recursion for repetitions > 0 +*/ +const recursionTry1 = (text = '', repetitions = 1) => { + if (repetitions > 0) { + return text; // base case: the text + } else { + const nextText = text + text; // build up the return value + const nextRepetitions = repetitions - 1; // step down towards the base case + return recursionTry1(nextText, nextRepetitions); // recurse! + } +}; + +/* -- recursion -- + + repeatString(text, repetitions) + base-case: repetitions is 0 + return: empty string + recursive case: repetitions is more than 0 + 1. add the text to the text + 2. subtract one from repetitions + return: repeatString(combinedText, smallerRepetitions) + +*/ + +// no go - wrote extra tests for different lenghts +// all lenghts return empty string +// ha! i never combine the retruned recursion with the current text value +// trying to fix this in recursionTry3 +const recursionTry2 = (text = '', repetitions = 1) => { + if (repetitions === 0) { + return ''; // 0 repetitions should be an empty string + } else { + const nextText = text + text; // build up the return value + const nextRepetitions = repetitions - 1; // step down towards the base case + return recursionTry2(nextText, nextRepetitions); // recurse! + } +}; + +/* -- recursion -- + + repeatString(text, repetitions) + base-case: repetitions is 0 + return: empty string + recursive case: repetitions is more than 0 + combinedText <- text + text + 2. subtract one from repetitions + return: text + repeatString(combinedText, smallerRepetitions) + +*/ + +// nope: repeats the text twice in each recursive call +// once with nextText +// and again just before returning +const recursionTry3 = (text = '', repetitions = 1) => { + if (repetitions === 0) { + return ''; // 0 repetitions should be an empty string + } else { + const nextText = text + text; // build up the return value + const nextRepetitions = repetitions - 1; // step down towards the base case + // combine the recursed value with text and return + return text + recursionTry3(nextText, nextRepetitions); + } +}; + +/* -- recursion -- + + try just passing the text into the recursive call + the problem was it was combined twice, once for nextText and once at return + this strategy only adds the text together once in the else + + repeatString(text, repetitions) + base-case: repetitions is 0 + return: empty string + recursive case: repetitions is more than 0 + 1. subtract one from repetitions + return: text + repeatString(text, smallerRepetitions) + +*/ + +// success! +// removed the extra repetition with nextText +const recursionTry4 = (text = '', repetitions = 1) => { + if (repetitions === 0) { + return ''; // 0 repetitions should be an empty string + } else { + const nextRepetitions = repetitions - 1; // step down towards the base case + // combine the recursed value with text and return + return text + recursionTry4(text, nextRepetitions); + } +}; + +// success again +const recursionTry4Ternary = (text = '', repetitions = 1) => { + return repetitions === 0 ? '' : text + recursionTry4(text, repetitions - 1); +}; + +// oops, tried this already. it was recursionTry2 +// wanted to see if i could combine the text before recursing +const recursionTry5 = (text = '', repetitions = 1) => { + if (repetitions === 0) { + return ''; // 0 repetitions should be an empty string + } else { + const nextText = text + text; // build up the return value + const nextRepetitions = repetitions - 1; // step down towards the base case + // combine the recursed value with text and return + return recursionTry5(nextText, nextRepetitions); + } +}; + +// =============== a for-of loop to control which solution(s) are tested =============== + +for (const solution of [ + // secretSolution, + // stub, + // forLoopTry1, + // forLoopTry2, + // forLoopTry3, // success! + // whileLoop, // success again + // withoutExtraVariable, + iterateWhileTooShort, // success + // recursionTry1, + // recursionTry2, + // recursionTry3, + recursionTry4, // success! + // recursionTry4Ternary, // success again + // recursionTry5, +]) { + // =============== test cases for this challenge =============== + + describe(solution.name + ': repeating a string:', () => { + describe('default parameters', () => { + it('repeat once if no repetitions is passed', () => { + expect(solution('asdf')).toEqual('asdf'); + }); + it('expect an empty string if no arguments are passed', () => { + expect(solution()).toEqual(''); + }); + }); + describe('an empty string', () => { + it('repeat an empty string 0 times -> ""', () => { + expect(solution('', 0)).toEqual(''); + }); + it('repeat an empty string 10 times -> ""', () => { + expect(solution('', 10)).toEqual(''); + }); + it('repeat an empty string 100 times -> ""', () => { + expect(solution('', 100)).toEqual(''); + }); + }); + describe('zero repetitions', () => { + it('repeat "asdf" 0 times -> ""', () => { + expect(solution('asdf', 0)).toEqual(''); + }); + it('repeat "tommywalk" 0 times -> ""', () => { + expect(solution('tommywalk', 0)).toEqual(''); + }); + }); + describe('standard use cases', () => { + it('repeating a longer phrase 3 times', () => { + expect(solution('go to school', 3)).toEqual( + 'go to schoolgo to schoolgo to school' + ); + }); + it('repeating a phrase with punctuation', () => { + expect(solution('"Go!", said Dr. Seuss?', 2)).toEqual( + '"Go!", said Dr. Seuss?"Go!", said Dr. Seuss?' + ); + }); + it('a string with special characters can be repeated', () => { + expect(solution('\\ \n \t s', 2)).toEqual('\\ \n \t s\\ \n \t s'); + }); + }); + }); +} + +// =============== a minified solution you can use to test your test cases =============== + +// prettier-ignore +function secretSolution(a = "", b = 1) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("number" != typeof b) { throw new TypeError("repetitions is not a number"); } if (0 > b) { throw new RangeError("repetitions is less than zero"); } if (!Number.isInteger(b)) { throw new RangeError("repetitions is not an integer"); } return Array(b).fill(a).join(""); } diff --git a/2-write-test-case/1-function-design/examples/study.json b/2-write-test-case/1-function-design/examples/study.json new file mode 100644 index 00000000..8fee6bba --- /dev/null +++ b/2-write-test-case/1-function-design/examples/study.json @@ -0,0 +1,8 @@ +{ + "--defaults": { + "directory": "stepped" + }, + "study": { + "save": false + } +} diff --git a/2-write-test-case/1-function-design/exercises/easy/count-down.test.js b/2-write-test-case/1-function-design/exercises/easy/count-down.test.js new file mode 100644 index 00000000..b1e3fae8 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/count-down.test.js @@ -0,0 +1,31 @@ +// #todo + +'use strict'; + +/** + * builds an array counting down from `start` to 0 + * @param {number} [start=0] - the number to count down from + * start must be an integer that is greater than 0 + * @returns {number[]} an array of all numbers from `start` to 0 + */ + +// -------- your solutions -------- +import {countdown} from './countDown.test.js' +for (const solution of [secretSolution]) { + // the main test suite for the function + //describe(solution.name + ': counts down to 0', () => { + //it('default parameter is 0 -> [0]', () => { + expect(solution()).toEqual([0]); + }); + it('0 -> [0]', () => { + expect(solution(0)).toEqual([0]); + }); + + it('0 <- [0]', () => { + expect(solution(0)).toEqual([0]) + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a = 0) { if ("number" != typeof a) throw new TypeError("start is not a number"); if (!Number.isInteger(a)) throw new Error("start is not an integer"); if (0 > a) throw new RangeError("start is less than 0"); const b = []; for (let c = a; 0 <= c; c--)b.push(c); return b } diff --git a/2-write-test-case/1-function-design/exercises/easy/count-up.test.js b/2-write-test-case/1-function-design/exercises/easy/count-up.test.js new file mode 100644 index 00000000..9c9036fb --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/count-up.test.js @@ -0,0 +1,72 @@ +/* eslint-disable spellcheck/spell-checker */ +/* eslint-disable linebreak-style */ + +/** .................... + * Builds an array counting up from 0 to `max` + * + * @param {number} [max=0] - the number to count up to + * max must be an integer that is greater than 0 + * @returns {number[]} an array of all numbers from 0 to `max` + */ + +// -------- your solutions -------- + +import { countUp } from "./countUp.test"; + +/** + * @param max + */ +/* function countUp(max = 0) { + if (!Number.isInteger(max) || max < 0) { + throw new Error('max must be a non-negative integer.'); + } + + const result = []; + for (let i = 0; i <= max; i++) { + result.push(i); + } + return result; +} */ +// const countup = () +// eslint-disable-next-line no-restricted-syntax +for (const solution of [countUp]) { + // the main test suite for the function + describe(`${solution.name}: counts up from 0`, () => { + it('default parameter is 0 -> [0]', () => { + const actual = solution(); + expect(actual).toEqual([0]); + }); + it('0 -> [0]', () => { + expect(solution(0)).toEqual([0]); + }); + it('1 -> [0, 1]', () => { + expect(solution(1)).toEqual([0, 1]); + }); + it('2 -> [0, 1, 3, 4, 6]', () => { + expect(solution(2)).toEqual([0, 1, 3, 4, 6]); + it('3 -> [0, 1, 2, 3, 4, 6, 7, 9]', () => { + expect(solution(3)).toEqual([0, 1, 2, 4, 6, 7, 9]); + }); + + '2 -> [0, 1, 3, 4, 6]', () => { + expect(solution(2)).toEqual([0, 1, 3, 4, 6]); + + // Test case for input 3 (modified to fail) + it('3 -> [0, 1, 3, 4, 6, 7, 9]', () => { + expect(solution(3)).toEqual([0, 1, 3, 4, 6, 7, 8]); // Expected output modified to [0, 1, 3, 4, 6, 7, 8] + }); + }; + }); + }); + + it('3 -> [0, 1, 2, 3, 4, 5]', () => { + expect(solution(3)).toEqual([0, 1, 2, 3, 4, 5]); + }); +} + +// minified solution for testing your tests +// prettier-ignore +/** + * @param a + */ +function secretSolution(a = 0) { if (typeof a !== "number") throw new TypeError("max is not a number"); if (!Number.isInteger(a)) throw new Error("max is not an integer"); if (a < 0) throw new RangeError("max is less than 0"); const b = []; for (let c = 0; c <= a; c++)b.push(c); return b; } diff --git a/2-write-test-case/1-function-design/exercises/easy/countDown.test.js b/2-write-test-case/1-function-design/exercises/easy/countDown.test.js new file mode 100644 index 00000000..bf5267fe --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/countDown.test.js @@ -0,0 +1,17 @@ +export {conutDown} from './count-down.test' + + + +function const = ( start = 0) => { + if ( start <= 0 ) { + return[0] + } + else{ + let solutionArray = [] + for ( let i=start; i>= ;i--){ + solutionArray.push(i) + + }; + return solutionArray + } +} \ No newline at end of file diff --git a/2-write-test-case/1-function-design/exercises/easy/countUp.test.js b/2-write-test-case/1-function-design/exercises/easy/countUp.test.js new file mode 100644 index 00000000..86a2866f --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/countUp.test.js @@ -0,0 +1,14 @@ +export {countUp} from './count-up.test' + +function countUp(max = 0) { + if (!Number.isInteger(max) || max < 0) { + throw new Error('max must be a non-negative integer.'); + } + + const result = []; + for (let i = 0; i <= max; i++) { + result.push(i); + } + return result; + } + diff --git a/2-write-test-case/1-function-design/exercises/easy/reverse-a-string.test.js b/2-write-test-case/1-function-design/exercises/easy/reverse-a-string.test.js new file mode 100644 index 00000000..755b1bfd --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/reverse-a-string.test.js @@ -0,0 +1,36 @@ +// #todo + +'use strict'; + +/** + * reverses a string + * @param {string} [toReverse=''] - the string to reverse + * @returns {string} the reversed argument + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + // the main test suite for the function + describe(solution.name + ': reverses a string', () => { + it('default parameter is an empty string -> ""', () => { + expect(solution()).toEqual(''); + }); + it('an empty string -> ""', () => { + expect(solution('')).toEqual(''); + }); + it('a string with all capital letters', () => { + expect(solution('ASDF')).toEqual('FDSA'); + }); + it('a string with number', () => { + expect(solution('12345').toEqual('54321')); + }); + it('a string with whitespace', () => { + expect(solution(' Hello, World! ')).toEqual(' !dlroW, olleH '); + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a = '') { if ("string" != typeof a) throw new TypeError("toReverse is not a string"); return a.split("").reverse().join("") } diff --git a/2-write-test-case/1-function-design/exercises/easy/reverse-and-case.test.js b/2-write-test-case/1-function-design/exercises/easy/reverse-and-case.test.js new file mode 100644 index 00000000..bd040041 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/reverse-and-case.test.js @@ -0,0 +1,54 @@ +// #todo + +'use strict'; + +/** + * reverses a string and sets all the characters to upper or lower case + * @param {string} [text=''] - the text to reverse and casify + * @param {boolean} [lowerCase=true] - lower case or upper case + * true: set the string to lower case + * false: set the string to upper case + * @returns {string} the reversed text in all lower or upper case + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe( + solution.name + ': reverses a string then sets to lower or upper case', + () => { + describe("the function's default parameters", () => { + it('second parameter defaults to true', () => { + expect(solution('asdf')).toEqual('fdsa'); + }); + it('first parameter defaults to an empty string', () => { + expect(solution()).toEqual(''); + }); + }); + // write the tests indicated by the comments + describe('when set to lower case', () => { + // when the text is an empty string + it(_, () => { + expect(solution(_, _)).toEqual(_); + }); + // when the text is all upper case + // when the text is all lower case + // when the text is mixed upper and lower case + // when the text contains punctuation + // when the text contains numbers + }); + describe('when set to upper case', () => { + // when the text is an empty string + // when the text is all upper case + // when the text is all lower case + // when the text is mixed upper and lower case + // when the text contains punctuation + // when the text contains numbers + }); + } + ); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a = "", b = !0) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("boolean" != typeof b) { throw new TypeError("lowerCase is not a boolean"); } let c = ""; for (let d = a.length - 1; 0 <= d; d--)c += a[d]; let d = ""; return d = b ? c.toLowerCase() : c.toUpperCase(), d } diff --git a/2-write-test-case/1-function-design/exercises/easy/set-the-case.test.js b/2-write-test-case/1-function-design/exercises/easy/set-the-case.test.js new file mode 100644 index 00000000..6d3762d6 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/set-the-case.test.js @@ -0,0 +1,51 @@ +// #todo + +'use strict'; + +/** + * sets all the characters in a string to upper or lower case + * @param {string} [text=''] - the text to casify + * @param {boolean} [lowerCase=true] - lower case or upper case + * true: set the string to lower case + * false: set the string to upper case + * @returns {string} the text in all lower or upper case + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': sets a text to lower or upper case', () => { + describe("the function's default parameters", () => { + it('second parameter defaults to true', () => { + expect(solution('asdf')).toEqual('asdf'); + }); + it('first parameter defaults to an empty string', () => { + expect(solution()).toEqual(''); + }); + }); + // write the tests indicated by the comments + describe('when set to lower case', () => { + // when the text is an empty string + it(_, () => { + expect(solution(_, _)).toEqual(_); + }); + // when the text is all upper case + // when the text is all lower case + // when the text is mixed upper and lower case + // when the text contains punctuation + // when the text contains numbers + }); + describe('when set to upper case', () => { + // when the text is an empty string + // when the text is all upper case + // when the text is all lower case + // when the text is mixed upper and lower case + // when the text contains punctuation + // when the text contains numbers + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a = "", b = !0) { if ("string" != typeof a) { throw new TypeError("text is not a string"); } if ("boolean" != typeof b) { throw new TypeError("lowerCase is not a boolean"); } let c = ""; return c = b ? a.toLowerCase() : a.toUpperCase(), c } diff --git a/2-write-test-case/1-function-design/exercises/easy/testing-paths-if-else-if-else.test.js b/2-write-test-case/1-function-design/exercises/easy/testing-paths-if-else-if-else.test.js new file mode 100644 index 00000000..c84c7fc8 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/testing-paths-if-else-if-else.test.js @@ -0,0 +1,50 @@ +// #todo + +'use strict'; + +/** + * returns a description of how similar two values are + * if they are strictly equal -> 'strictly equal' + * if they have the same type -> 'same type' + * else -> 'totally different' + * @param {any} val1 + * @param {any} val2 + * @returns {string} the values' solution + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': determines how similar two values are', () => { + describe('when values are strictly equal', () => { + it('two identical strings -> "strictly equal"', () => { + expect(solution('hello', 'hello')).toEqual(_); + }); + it('two identical numbers -> "strictly equal"', () => { + // 1, 1.0 + }); + it('two identical booleans -> "strictly equal"', () => {}); + }); + describe('when values have the same type', () => { + it('two different strings -> "same type"', () => { + expect(_).toEqual('same type'); + }); + it('two different numbers -> "same type"', () => { + expect(_).toEqual(_); + }); + it('two different booleans -> "same type"', () => {}); + }); + describe('when values are nothing alike', () => { + it('values that are obviously different', () => { + _(_(null, 4))._(_); + }); + it('values that can be confusing', () => { + // "4" and 4 + }); + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a, b) { let c = ""; return c = a === b ? "strictly equal" : typeof a == typeof b ? "same type" : "totally different", c } diff --git a/2-write-test-case/1-function-design/exercises/easy/testing-paths-if-else.test.js b/2-write-test-case/1-function-design/exercises/easy/testing-paths-if-else.test.js new file mode 100644 index 00000000..7bb3b916 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/testing-paths-if-else.test.js @@ -0,0 +1,58 @@ +// #todo + +'use strict'; + +/** + * returns true if the value is truthy + * returns false if the value is falsy + * @param {any} value + * @returns {boolean} + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + /* Execution Paths + when testing conditionals, you need to be test all paths + */ + describe(solution.name + ': determines if a value is truthy', () => { + describe('solution can identify truthy values', () => { + it('non-empty strings -> true', () => { + const actual = solution(_); + expect(actual).toEqual(true); + }); + it('numbers that are not 0 or NaN -> true', () => { + const actual = _; + expect(actual).toEqual(true); + }); + it('true -> true', () => { + expect(solution(_)).toEqual(true); + }); + }); + describe('solution can identify falsy values', () => { + it('"" -> flase', () => { + _; + }); + it('0 -> false', () => { + _; + }); + it('NaN -> false', () => { + _; + }); + it('false -> false', () => { + _; + }); + it('undefined -> false', () => { + _; + }); + it('null -> false', () => { + _; + }); + }); + }); +} + +// minified solution for testing your tests + +// prettier-ignore +function secretSolution(value) { return value ? true : false; } diff --git a/2-write-test-case/1-function-design/exercises/easy/testing-paths-sequential-conditionals.test.js b/2-write-test-case/1-function-design/exercises/easy/testing-paths-sequential-conditionals.test.js new file mode 100644 index 00000000..391094b5 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/easy/testing-paths-sequential-conditionals.test.js @@ -0,0 +1,42 @@ +// #todo + +'use strict'; + +/** + * converts two boolean values into a binary string + * true become "1", false becomes "0" + * @param {boolean} [a=false] - the left value + * @param {boolean} [b=false] - the right value + * @returns {string} + * @example + * // true, false --> '10' + * // false, false --> '00' + * // false, true --> '01' + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + // this function only 4 possible combinations of arguments + // it's possible test them all and have 100% confidence in the function + describe(solution.name + ': converts two booleans to binary', () => { + it('true, true --> "11"', () => { + const actual = solution(_, _); + expect(actual).toEqual(_); + }); + it('true, false --> "10"', () => { + const actual = _; + expect(actual).toEqual('10'); + }); + it('false, true --> "01"', () => { + const actual = _; + _; + }); + it('_', () => {}); + }); +} + +// minified solution for testing your tests + +// prettier-ignore +function secretSolution(c = false, a = false) { if ("boolean" != typeof c) { throw new TypeError("a is not boolean"); } if ("boolean" != typeof a) { throw new TypeError("b is not boolean"); } let b = ""; return b += c ? "1" : "0", b += a ? "1" : "0", b } diff --git a/2-write-test-case/1-function-design/exercises/hard/numbery-numberify.test.js b/2-write-test-case/1-function-design/exercises/hard/numbery-numberify.test.js new file mode 100644 index 00000000..d293ce7d --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/hard/numbery-numberify.test.js @@ -0,0 +1,27 @@ +// #todo + +'use strict'; + +/** + * takes an array of strings and returns a new array of numbers + * the new array contains all the numbery strings, cast to numbers + * does not modify the argument (no side-effects) + * @param {string[]} arrayOfStrings - the array of strings + * @returns {number[]} an array containing only numbers, and not NaN + * @example + * ['1', '2', 'e', '.'] // --> [1, 2] + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': _', () => { + describe('_', () => { + it('_', () => {}); + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a) { if (!Array.isArray(a)) { throw new TypeError("arrayOfStrings is not an array"); } const b = a.some(a => "string" != typeof a); if (b) { throw new TypeError("arrayOfStrings contains non-strings"); } const c = a.map(a => +a), d = c.filter(a => !Number.isNaN(a)); return d } diff --git a/2-write-test-case/1-function-design/exercises/hard/passing-objects.test.js b/2-write-test-case/1-function-design/exercises/hard/passing-objects.test.js new file mode 100644 index 00000000..31c13217 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/hard/passing-objects.test.js @@ -0,0 +1,70 @@ +// #todo + +'use strict'; + +/** + * returns a new array containing all passing objects + * passing objects have a property "pass" with the value true + * if an object does not have the entry "pass": true, it is not passing + * @param {Array.} arr - an array of objects to filer + * @returns {Array.} a new array containing only passing objects + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': filters out non-passing objects', () => { + describe('correctly filters an array', () => { + it('an empty array returns an empty array', () => { + const actual = solution(_); + expect(_).toEqual(_); + }); + it('keeps all entries when all are passing', () => { + const actual = solution([{ pass: _ }, { pass: _ }]); + expect(actual).toEqual([{ pass: true }, { pass: true }]); + }); + it('removes all entries when all are not passing', () => { + const actual = solution([{ pass: _ }, { pass: _ }]); + expect(actual).toEqual(_); + }); + it('removes only not-passing entries', () => { + const actual = solution([ + { pass: true }, + { pass: false }, + { pass: true }, + ]); + expect(actual).toEqual([{ _: _ }, { _: _ }]); + }); + it('removes entries with a truthy, but not true, .pass value', () => { + const actual = solution([{ pass: 100 }, { pass: 'hello' }, { _: _ }]); + expect(actual).toEqual([{ pass: true }]); + }); + it('removes entries with no .pass property', () => { + const actual = solution([ + { hello: _ }, + { bye: _ }, + { pass: _ }, + { passing: _ }, + ]); + expect(actual)._.deep._([{ pass: true }]); + }); + }); + describe('does not modify the argument', () => { + it('returns a new array', () => { + const arg = []; + const actual = solution(arg); + const areNotTheSameArray = arg !== actual; + expect(areNotTheSameArray).toEqual(true); + }); + it('does not modify the argument', () => { + const arg = [{ pass: true }, { pass: false }, { hello: 'good bye' }]; + solution(arg); + expect(arg).toEqual([{ _: _ }, { _: _ }, { _: _ }]); + }); + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a) { if (!Array.isArray(a)) { throw new TypeError("arr is not an array"); } if (!a.every(a => Object(a) === a)) { throw new TypeError("arr is not an array of objects"); } const b = a.filter(a => !0 === a.pass); return b } diff --git a/2-write-test-case/1-function-design/exercises/hard/sum-numbery.test.js b/2-write-test-case/1-function-design/exercises/hard/sum-numbery.test.js new file mode 100644 index 00000000..d24931ac --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/hard/sum-numbery.test.js @@ -0,0 +1,43 @@ +// #todo + +'use strict'; + +/** + * sums all numbery strings in an array of strings + * returns 0 if the array is empty + * it does not modify the original array (no side-effects) + * @param {string[]} arr - the array of strings + * @returns {number} the sum of all numbery strings + */ + +// -------- your solutions -------- + +const mapFilterReduce = (arr) => { + // these work, you need to pass them to the right array methods + const isNotNaN = (entry) => !Number.isNaN(entry); + const sumNumbers = (acc, next) => acc + next; + const castToNumber = (entry) => Number(entry); + + // fill in the array methods and pass in the correct logic + const sumOfNumberies = arr._(_)._(_)._(_, _); + + return sumOfNumberies; +}; + +// -------- your solutions -------- + +for (const solution of [ + secretSolution, + // mapFilterReduce, +]) { + describe(solution.name + ': _', () => { + describe('_', () => { + it('_', () => {}); + }); + }); +} + +// minified solution for testing your tests + +// prettier-ignore +function secretSolution(a) { if (!Array.isArray(a)) { throw new TypeError("arr is not an array"); } const b = a.some(a => "string" != typeof a); if (b) { throw new TypeError("arr contains non-strings"); } const c = a.map(a => +a).filter(a => !Number.isNaN(a)).reduce((a, b) => a + b, 0); return c } diff --git a/2-write-test-case/1-function-design/exercises/medium/fizzbuzz-1.test.js b/2-write-test-case/1-function-design/exercises/medium/fizzbuzz-1.test.js new file mode 100644 index 00000000..1f679566 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/medium/fizzbuzz-1.test.js @@ -0,0 +1,72 @@ +// #todo + +'use strict'; + +/** + * checks if a number is divisible by 5, 3 or both: + * numbers divisible by 3 and 5 return "fizzbuzz" + * numbers divisible by only 3 return "fizz" + * numbers divisible by only 5 return "buzz" + * all other numbers are returned un-changed + * @param {number} [num=0] - the number to convert + * num must be an integer greater than or equal to 0 + * @returns {number|string} either "fizz", "buzz", "fizzbuzz" or the original number + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': fizbuzzish', () => { + describe('default parameter is 0', () => { + it('returns "fizzbuzz" when no argument is passed', () => + expect(solution()).toEqual('fizzbuzz')); + }); + + describe('not divisible by 3 or 5', () => { + it('1 -> 1', () => { + expect(solution(1)).toEqual(1); + }); + it('2 -> 2', () => { + expect(solution(2)).toEqual(2); + }); + // write more tests in this category + }); + + describe('only divisible by only 3', () => { + const expectedValue = 'fizz'; + it('3 -> "fizz"', () => { + expect(solution(3)).toEqual(expectedValue); + }); + it('6 -> "fizz"', () => { + expect(solution(6)).toEqual(expectedValue); + }); + // write more tests in this category + }); + + describe('only divisible by only 5', () => { + const expectedValue = 'buzz'; + it('5 -> "buzz"', () => { + expect(solution(5)).toEqual(expectedValue); + }); + it('10 -> "buzz"', () => { + expect(solution(10)).toEqual(expectedValue); + }); + // write more tests in this category + }); + + describe('divisible by 5 and 3', () => { + const expectedValue = 'fizzbuzz'; + it('15 -> "fizzbuzz"', () => { + expect(solution(15)).toEqual(expectedValue); + }); + it('30 -> "fizzbuzz"', () => { + expect(solution(30)).toEqual(expectedValue); + }); + // write more tests in this category + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a = 0) { if ("number" != typeof a) { throw new TypeError("num is not a number"); } if (0 > a) { throw new RangeError("num is less than 0"); } if (!Number.isInteger(a)) { throw new RangeError("num is not an integer"); } return 0 == a % 3 && 0 == a % 5 ? "fizzbuzz" : 0 == a % 3 ? "fizz" : 0 == a % 5 ? "buzz" : a } diff --git a/2-write-test-case/1-function-design/exercises/medium/fizzbuzz-2.test.js b/2-write-test-case/1-function-design/exercises/medium/fizzbuzz-2.test.js new file mode 100644 index 00000000..9fb5c99c --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/medium/fizzbuzz-2.test.js @@ -0,0 +1,119 @@ +// #todo + +'use strict'; + +/** + * returns an array with length "max" + * each index that's divisible by 3 and 5 stores "fizzbuzz" + * each index that's divisible by only 3 stores "fizz" + * each index that's divisible by only 5 stores "buzz" + * all other indexes store the same number as that index + * @param {number} [max=0] - how many numbers to check + * max must be an integer greater than or equal to 0 + * @returns {(number|string)[]} an array of length max + */ + +// -------- your solutions -------- + +const whileLoop = (max) => { + let countUp = _; + const result = []; + while (_) { + const nextEntry = countUp % 15 === 0 ? '_' : _ ? 'buzz' : _ ? 'fizz' : _; + result.push(nextEntry); + } + return result; +}; + +/* describe this solution's strategy + */ +const oneLineforLoop = (max) => { + const result = []; + for (let i = 0; i < _; ) + result._((++i % _ ? '' : '_') + (i % _ ? '' : '_') || i); + return result; + + // https://codeburst.io/javascript-breaking-down-the-shortest-possible-fizzbuzz-answer-94a0ad9d128a +}; + +/* describe this solution's strategy + */ +const manySmallFunctions = (max) => { + const threeDivides = (n) => n % _ === 0; + const fiveDivides = (n) => n % _ === 0; + const fifteenDivides = (n) => n % _ === 0; + + const fizzbuzzOrNumber = (num) => { + if (_) { + return 'fizzbuzz'; + } else if (_) { + return 'fizz'; + } else if (_) { + return 'buzz'; + } else { + return num; + } + }; + + // https://stackoverflow.com/a/33352604 + const arrayOfIndexes = [...Array(max).keys()]; + const fizzBuzzedArray = arrayOfIndexes.map(_); + return fizzBuzzedArray; +}; + +// -------- your solutions -------- + +for (const solution of [ + secretSolution, + // whileLoop, + // oneLineforLoop, + // manySmallFunctions, +]) { + describe(solution.name + ': fizzbuzz', () => { + describe('numbers divisible by 3', () => { + it('3 should return an array with the first 3 values', () => { + expect(solution(3)).toEqual(['fizzbuzz', 1, 2]); + }); + // write more of these + }); + describe('numbers divisible by neither 3 nor 5', () => { + it('4 should return an array with the first 4 values', () => { + expect(solution(4)).toEqual(['fizzbuzz', 1, 2, 'fizz']); + }); + // write more of these + }); + describe('numbers divisible by 5', () => { + it('5 should return an array with the first 5 values', () => { + expect(solution(5)).toEqual(['fizzbuzz', 1, 2, 'fizz', 4]); + }); + // write more of these + }); + describe('numbers divisible by 3 and 5', () => { + it('15 should return an array with the first 15 values', () => { + expect(solution(15)).toEqual([ + 'fizzbuzz', + 1, + 2, + 'fizz', + 4, + 'buzz', + 'fizz', + 7, + 8, + 'fizz', + 'buzz', + 11, + 'fizz', + 13, + 14, + ]); + }); + // write more of these + }); + }); +} + +// minified solution for testing your tests + +// prettier-ignore +function secretSolution(a) { if ("number" != typeof a) { throw new TypeError("max is not a number"); } if (0 > a) { throw new RangeError("max is less than 0"); } if (!Number.isInteger(a)) { throw new RangeError("max is not an integer"); } const b = []; for (let c = 0; c < a; c++) { 0 == c % 3 && 0 == c % 5 ? b.push("fizzbuzz") : 0 == c % 3 ? b.push("fizz") : 0 == c % 5 ? b.push("buzz") : b.push(c); } return b; } diff --git a/2-write-test-case/1-function-design/exercises/medium/only-even-numbers.test.js b/2-write-test-case/1-function-design/exercises/medium/only-even-numbers.test.js new file mode 100644 index 00000000..5c557f69 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/medium/only-even-numbers.test.js @@ -0,0 +1,24 @@ +// #todo + +'use strict'; + +/** + * removes all odd numbers from an array of numbers + * does not modify the argument (no side-effects) + * @param {number[]} arrayOfNumbers - the array of numbers to filter + * @returns {number[]} an array with only even numbers + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': _', () => { + describe('_', () => { + it('_', () => {}); + }); + }); +} + +// minified solution for testing your tests +// prettier-ignore +function secretSolution(a) { if (!Array.isArray(a)) { throw new TypeError("arrayOfNumbers is not an array"); } const b = a.some(a => "number" != typeof a); if (b) { throw new TypeError("arrayOfNumbers does not contain only numbers"); } const c = a.filter(a => 0 == a % 2); return c } diff --git a/2-write-test-case/1-function-design/exercises/medium/reverse-concatenate.test.js b/2-write-test-case/1-function-design/exercises/medium/reverse-concatenate.test.js new file mode 100644 index 00000000..f0c1d207 --- /dev/null +++ b/2-write-test-case/1-function-design/exercises/medium/reverse-concatenate.test.js @@ -0,0 +1,25 @@ +// #todo + +'use strict'; + +/** + * A function that takes an array of strings, reverses it, and combines the strings + * it does not modify the original array + * @param {string[]} arrayOfStrings - an array of strings to concatenate, in reverse order + * @returns {string} - the array elements joined together, in reverse order + */ + +// -------- your solutions -------- + +for (const solution of [secretSolution]) { + describe(solution.name + ': _', () => { + describe('_', () => { + it('_', () => {}); + }); + }); +} + +// minified solution for testing your tests + +// prettier-ignore +function secretSolution(a) { if (!Array.isArray(a)) { throw new TypeError("arrayOfStrings is not an array"); } const b = a.some(a => "string" != typeof a); if (b) { throw new TypeError("arrayOfStrings does not contain only strings"); } return [...a].reverse().reduce((a, b) => a + b, "") } diff --git a/2-write-test-case/1-function-design/study.json b/2-write-test-case/1-function-design/study.json new file mode 100644 index 00000000..af285a47 --- /dev/null +++ b/2-write-test-case/1-function-design/study.json @@ -0,0 +1,7 @@ +{ + "stepped": { + "save": true, + "type": "module", + "tests": [".spec.js"] + } +} diff --git a/2-write-test-case/2-document-and-pass/README.md b/2-write-test-case/2-document-and-pass/README.md new file mode 100644 index 00000000..b2cebf17 --- /dev/null +++ b/2-write-test-case/2-document-and-pass/README.md @@ -0,0 +1,3 @@ +# Document and Pass + +(it's [over here](https://github.com/HackYourFutureBelgium/document-and-pass)) diff --git a/2-write-test-case/3-fuzz-testing/README.md b/2-write-test-case/3-fuzz-testing/README.md new file mode 100644 index 00000000..c774cd7f --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/README.md @@ -0,0 +1,62 @@ +# Fuzz Testing + +Fuzz testing is when you use random inputs to test your function. Not completely random, the test cases will all have the correct structure but will have random values. so instead of using an array that looks like this: + +- `[ "spain", "portugal", "france", "italy" ]` + +You would generate random arrays of strings like this one and use them to test your function: + +- `[ "8 #@H=+ /", "~asd i-b", ".089}|", "q [=-" ]` + +Why practice fuzz testing? + +- You can test your functions against hundreds of more test cases than you ever could with manually written tests +- Randomly generated tests will catch mistakes you would never think of writing a test for +- Interpreting the test results will encourage you to think more abstractly since the test cases don't have any meaning + +To study these exercises you'll need to have your console open, all test results will be logged there. + +Enjoy! + +--- + +## The Exercises + +There's a lot of exercises in here, you should try to find at least 2 solutions to 3 of them. Here's what you can expect from each set of exercises: + +- **/arrays-of-primitives**: Exactly that, functions that operate on arrays of primitive values. +- **/arrays-of-objects**: Exactly that, functions operate on arrays of objects. +- **/string-manipulation**: Practice doing things with strings. This can be a good chance to practice Regular Expressions. +- **/mathy**: Challenges that do mathy things with numbers, like generating Fibonacci sequences. +- **/guards**: Some of the tests will contain invalid arguments. Besides passing valid test cases, your solution will need to throw a specific error when the arguments are invalid. +- **/mysteries**: No instructions, no JSDoc, no starter code. Just you, an empty function, and some test cases. Good luck! + +--- + +## Exercise Files + +Each exercise folder has this folder structure, you will see this in VSCode but not in the browser: + +``` +/exercise-name + /solutions + /solution-name.js + /fuzz.js - (required) + /jsdoc.js + /README.md +``` + +The folder's name will be used to automatically name your solution functions: + +- `/exercise-name` -> `const exerciseName = () => {};` + +Here's a some more about each item an exercise folder: + +- **/fuzz.js** (required): The most important file. If a folder has a file named `fuzz.js` then it will appear as an exercise in the browser. `fuzz.js` must export two functions: + - **`solution`**: A solution to this exercise that will be used to generate random test cases. + - **`args`**: A function that returns an array of random arguments for testing your code. This function takes the [chance](https://chancejs.com/) library as an argument to help generate random values. +- **/solutions**: Complete or partial solutions to this challenge, you can easily switch between solutions when studying in the browser: + - You can create new solution files from the browser. + - Some exercises already have a few starter solutions with blanks to complete. +- **/jsdoc.js**: A JSDoc comment describing the challenge. If it exists, it will be displayed above the editor. +- **/README.md**: A markdown description of the challenge. If it exists it will be displayed above the solutions. diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-objects/passing-objects/README.md b/2-write-test-case/3-fuzz-testing/arrays-of-objects/passing-objects/README.md new file mode 100644 index 00000000..c201fc31 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-objects/passing-objects/README.md @@ -0,0 +1,10 @@ +# Passing Objects + +Write a function which keeps only passing objects + +- ARGS: an array of objects, most will have a `pass` property: + - `{pass: true}` + - The `pass` property can have any type + - ... but not all objects have a `pass` property! if an entry does not have a `pass` property, you still want to keep it +- RETURN: an empty array, or an array containing only passing object + - passing objects look like this: `{ pass: true }` diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-objects/passing-objects/fuzz.js b/2-write-test-case/3-fuzz-testing/arrays-of-objects/passing-objects/fuzz.js new file mode 100644 index 00000000..50d8fce3 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-objects/passing-objects/fuzz.js @@ -0,0 +1,37 @@ +export const solution = (arr) => + arr.filter((obj) => !obj.hasOwnProperty('pass') || obj.pass === true); + +export const args = (chance) => { + const rando = () => { + const rando = Math.random(); + if (rando < 0.1) { + return chance.word(); + } else if (rando < 0.6) { + return chance.integer(); + } else if (rando < 0.7) { + return true; + } else if (rando < 0.8) { + return false; + } else if (rando < 0.9) { + return null; + } + }; + chance.mixin({ + passable: function () { + const entry = { + title: chance.sentence({ words: chance.integer({ min: 1, max: 5 }) }), + body: chance.paragraph(), + }; + if (Math.random() < 0.8) { + entry.pass = rando(); + } + return entry; + }, + }); + const passableObjects = []; + const arraySize = chance.integer({ min: 0, max: 10 }); + for (let i = 0; i < arraySize; i++) { + passableObjects.push(chance.passable()); + } + return [passableObjects]; +}; diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/fuzz.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/fuzz.js new file mode 100644 index 00000000..cc92e385 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/fuzz.js @@ -0,0 +1,19 @@ +export const solution = (arr) => + arr.map((entry) => Number(entry)).filter((entry) => !Number.isNaN(entry)); + +export const args = (chance) => { + const rando = () => { + const probs = Math.random(); + return probs < 0.4 + ? chance.word({ syllables: 3 }) + : probs < 0.6 + ? String(chance.floating({ min: -999999999, max: 999999999 })) + : String(chance.integer({ min: -999999999, max: 999999999 })); + }; + const strings = []; + const arraySize = Math.floor(Math.random() * 10); + for (let i = 0; i < arraySize; i++) { + strings.push(rando()); + } + return [strings]; +}; diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/jsdoc.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/jsdoc.js new file mode 100644 index 00000000..01cbcbaa --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/jsdoc.js @@ -0,0 +1,7 @@ +/** + * takes an array of strings and returns a new array + * the new array contains all the numbery strings, cast to number + * does not modify the argument + * @param {string[]} arr - the array of strings + * @returns {number[]} an array containing numbers that aren't NaN + */ diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/solutions/map-filter.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/solutions/map-filter.js new file mode 100644 index 00000000..7e9ffba9 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/numbery-numberify/solutions/map-filter.js @@ -0,0 +1,11 @@ +export const mapFilter = (arr) => { + // these work, you need to use them with the right array methods + const isNotNaN = (entry) => !Number.isNaN(entry); + const castToNumber = (entry) => Number(entry); + + // fill in the array methods and which logic to use + const numbers = _._(_); + const allValidNumbers = _._(_); + + return allValidNumbers; +}; diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/README.md b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/README.md new file mode 100644 index 00000000..08708e18 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/README.md @@ -0,0 +1,5 @@ +# Reverse-Concatenate + +Write a function that takes in an array of strings and returns a single string. + +The returned string should be all the array entries stuck together, in reverse order. diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/fuzz.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/fuzz.js new file mode 100644 index 00000000..6efe5948 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/fuzz.js @@ -0,0 +1,11 @@ +export const solution = (arr) => + [...arr].reverse().reduce((acc, next) => acc + next, ''); + +export const args = (chance) => { + const newArgument = []; + const arraySize = Math.floor(Math.random() * 10); + for (let i = 0; i < arraySize; i++) { + newArgument.push(chance.word()); + } + return [newArgument]; +}; diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/jsdoc.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/jsdoc.js new file mode 100644 index 00000000..b1f8d7e7 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/jsdoc.js @@ -0,0 +1,5 @@ +/** + * A function that takes an array of strings, reverses it, and combines the strings + * @param {array} arr - an array of strings to concatenate, backwards + * @returns {string} - the array elements joined together + */ diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/solutions/array-methods.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/solutions/array-methods.js new file mode 100644 index 00000000..749ec557 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/reverse-concatenate/solutions/array-methods.js @@ -0,0 +1,4 @@ +export const arrayMethods = (arr) => + [...arr] // .reverse has a side effect, so copy the argument + .reverse() // reverse the copied array + .reduce(_); diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/README.md b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/README.md new file mode 100644 index 00000000..fe30e086 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/README.md @@ -0,0 +1,3 @@ +# Sum Even Numbers + +Write a function that takes in an array of numbers, filters out the odd ones, and adds the remaining even numbers. diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/fuzz.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/fuzz.js new file mode 100644 index 00000000..796ad755 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/fuzz.js @@ -0,0 +1,11 @@ +export const solution = (arrOfNums) => + arrOfNums.filter((num) => num % 2 === 0).reduce((sum, next) => sum + next, 0); + +export const args = () => { + const randomNumbers = []; + const arraySize = Math.floor(Math.random() * 10); + for (let i = 0; i < arraySize; i++) { + randomNumbers.push(Math.floor(Math.random() * 100)); + } + return [randomNumbers]; +}; diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/jsdoc.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/jsdoc.js new file mode 100644 index 00000000..22d94ae7 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/jsdoc.js @@ -0,0 +1,5 @@ +/** + * sums all even numbers in an array + * @param {array} arr - an array of numbers + * @returns {number} - the sum of all even numbers in argument + */ diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/solutions/filter-reduce.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/solutions/filter-reduce.js new file mode 100644 index 00000000..b76bf980 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-even-numbers/solutions/filter-reduce.js @@ -0,0 +1,5 @@ +export const filterReduce = (arr) => + arr + .reverse() + .filter(e=>e) // keep the even numbers + .reduce(e=>e); // add all the even numbers diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/fuzz.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/fuzz.js new file mode 100644 index 00000000..c7569c17 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/fuzz.js @@ -0,0 +1,22 @@ +export const solution = (arr) => + arr + .map((entry) => Number(entry)) + .filter((entry) => !Number.isNaN(entry)) + .reduce((acc, next) => acc + next, 0); + +export const args = (chance) => { + const rando = () => { + const probs = Math.random(); + return probs < 0.4 + ? chance.word({ syllables: 3 }) + : probs < 0.6 + ? String(chance.floating({ min: -999999999, max: 999999999 })) + : String(chance.integer({ min: -999999999, max: 999999999 })); + }; + const strings = []; + const arraySize = Math.floor(Math.random() * 10); + for (let i = 0; i < arraySize; i++) { + strings.push(rando()); + } + return [strings]; +}; diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/jsdoc.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/jsdoc.js new file mode 100644 index 00000000..9fef420c --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/jsdoc.js @@ -0,0 +1,6 @@ +/** + * sums all numbery strings in an array + * returns 0 if the array is empty + * @param {string[]} arr - the array of strings + * @returns {number} the sum of all numbery strings + */ diff --git a/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/solutions/map-filter-reduce.js b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/solutions/map-filter-reduce.js new file mode 100644 index 00000000..1efd90fc --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/arrays-of-primitives/sum-numbery/solutions/map-filter-reduce.js @@ -0,0 +1,11 @@ +export const mapFilterReduce = (arr) => { + // these work, you need to pass them to the right array methods + const isNotNaN = (entry) => !Number.isNaN(entry); + const sumNumbers = (acc, next) => acc + next; + const castToNumber = (entry) => Number(entry); + + // fill in the array methods and pass in the correct logic + const allValidNumbers = arr._(_)._(_)._(_, _); + + return allValidNumbers; +}; diff --git a/2-write-test-case/3-fuzz-testing/mathy/factorial/fuzz.js b/2-write-test-case/3-fuzz-testing/mathy/factorial/fuzz.js new file mode 100644 index 00000000..c29610d2 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mathy/factorial/fuzz.js @@ -0,0 +1,29 @@ +// solution by Angus Croll: https://github.com/angus-c/literary.js/blob/master/book/austen/factorial.js + +// prettier-ignore +var factorial = (function() { + //She declared the ledger to be very plain. But with the happiest prospects! + var ledger = {}; + + return function reckoning(quantity) { + if (isNaN(quantity)) { + console.log("I have not the pleasure of understanding you."); + return; + } + //It is a truth universally acknowledged that two values can only be judged + //truly agreeable by means of the treble equal symbol... + if (quantity === 0) { + return 1; + } + //Mr Crockford teaches that we should be wary of inherited property... + if (ledger.hasOwnProperty(quantity)) { + return ledger[quantity]; + } + //No sooner was each function finished than the next one began! + return ledger[quantity] = quantity * reckoning(quantity - 1); + }; +})(); + +export { factorial as solution }; + +export const args = (chance) => [chance.integer({ min: 0, max: 99 })]; diff --git a/2-write-test-case/3-fuzz-testing/mathy/factorial/jsdoc.js b/2-write-test-case/3-fuzz-testing/mathy/factorial/jsdoc.js new file mode 100644 index 00000000..028a18d5 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mathy/factorial/jsdoc.js @@ -0,0 +1,18 @@ +/** + * A function that calculates the factorial for a given number. + * + * @param {number} n - An integer to factorialize. + * @returns {number} The factorial of n. + * + * @example + * factorial(0); // -> 0 + * + * @example + * factorial(1); // -> 1 + * + * @example + * factorial(2); // -> 2 + * + * @example + * factorial(5); // -> 120 + */ diff --git a/2-write-test-case/3-fuzz-testing/mathy/fibonacci/fuzz.js b/2-write-test-case/3-fuzz-testing/mathy/fibonacci/fuzz.js new file mode 100644 index 00000000..7443c727 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mathy/fibonacci/fuzz.js @@ -0,0 +1,39 @@ +// solution by Angus Croll: https://github.com/angus-c/literary.js/blob/master/book/shakespeare/fibonacci.js + +// prettier-ignore +function theSeriesOfFIBONACCI(theSize) { + + //a CALCKULATION in two acts + //employ'ng the humourous logick of JAVA-SCRIPTE + + //Dramatis Personae + var theResult; //an ARRAY to contain THE NUMBERS + var theCounter; //a NUMBER, serv'nt to the FOR LOOP + + //ACT I: in which a ZERO is added for INITIATION 11 + + //[ENTER: theResult] + + //Upon the noble list bestow a zero + var theResult = [0]; + + //ACT II: a LOOP in which the final TWO NUMBERS are QUEREED and SUMM'D 18 + + //[ENTER: theCounter] 20 + + //Commence at one and venture o'er the numbers + for (theCounter = 1; theCounter < theSize; theCounter++) { + //By divination set adjoining members + theResult[theCounter] = (theResult[theCounter-1] || 1) + + theResult[Math.max(0, theCounter-2)]; + } + + //'Tis done, and here's the answer + return theResult; + + //[Exeunt] +} + +export { theSeriesOfFIBONACCI as solution }; + +export const args = (chance) => [chance.integer({ min: 0, max: 99 })]; diff --git a/2-write-test-case/3-fuzz-testing/mathy/fibonacci/jsdoc.js b/2-write-test-case/3-fuzz-testing/mathy/fibonacci/jsdoc.js new file mode 100644 index 00000000..6eb9c291 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mathy/fibonacci/jsdoc.js @@ -0,0 +1,18 @@ +/** + * A function that returns the first n numbers of the Fibonacci sequence. + * + * @param {number} n - An integer indicating how many Fibonacci numbers to calculate. + * @returns {number[]} An array containing the first n Fibonacci numbers. + * + * @example + * fibonacci(0); // -> [] + * + * @example + * fibonacci(1); // -> [0] + * + * @example + * fibonacci(2); // -> [0, 1] + * + * @example + * fibonacci(5); // -> [0, 1, 1, 2, 3] + */ diff --git a/2-write-test-case/3-fuzz-testing/mathy/happy-number/fuzz.js b/2-write-test-case/3-fuzz-testing/mathy/happy-number/fuzz.js new file mode 100644 index 00000000..43d30aee --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mathy/happy-number/fuzz.js @@ -0,0 +1,22 @@ +// solution by Angus Croll: https://github.com/angus-c/literary.js/blob/master/book/woolf/happy.js + +// prettier-ignore +function happy(number) { + + var next, numerals, noneOfThese = []; + + //unless the number was nothing; or one; or unless it had been already tried + while (number && number != 1 && noneOfThese[number] == null) { + next = 0, numerals = String(number).split('') + //digits forced apart, now multiplied, now cast aside; in service of what? + while (next = next+numerals[0]*numerals[0], numerals.shift(), numerals.length); + noneOfThese[number] = true, number = next + } + + //to be one; alone; happily + return number == 1 +} + +export { happy as solution }; + +export const args = (chance) => [chance.integer({ min: 0, max: 99 })]; diff --git a/2-write-test-case/3-fuzz-testing/mathy/happy-number/jsdoc.js b/2-write-test-case/3-fuzz-testing/mathy/happy-number/jsdoc.js new file mode 100644 index 00000000..e69de29b diff --git a/2-write-test-case/3-fuzz-testing/mathy/prime-numbers/fuzz.js b/2-write-test-case/3-fuzz-testing/mathy/prime-numbers/fuzz.js new file mode 100644 index 00000000..b1adf78e --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mathy/prime-numbers/fuzz.js @@ -0,0 +1,36 @@ +// solution by Angus Croll: https://github.com/angus-c/literary.js/blob/master/book/carroll/prime.js + +// prettier-ignore + +// They speak (I know) of finials, newels and balustrades +// of hidden spandrels and eternally clambering, broad-gaited beasts... + +var monstersAscendingAStaircase = function (numberOfSteps) { + var stairs = [], + stepsUntrodden = []; + var largestGait = Math.sqrt(numberOfSteps); + + // A succession of creatures mount the stairs; + // each creature's stride exceeds that of its predecessor. + for (var i = 2; i <= largestGait; i++) { + if (!stairs[i]) { + for (var j = i * i; j <= numberOfSteps; j += i) { + stairs[j] = 'stomp'; + } + } + } + + // Long-limbed monsters won't tread on prime-numbered stairs. + for (var i = 2; i <= numberOfSteps; i++) { + if (!stairs[i]) { + stepsUntrodden.push(i); + } + } + + // Here, then, is our answer. + return stepsUntrodden; +}; + +export { monstersAscendingAStaircase as solution }; + +export const args = (chance) => [chance.integer({ min: 0, max: 99 })]; diff --git a/2-write-test-case/3-fuzz-testing/mathy/prime-numbers/jsdoc.js b/2-write-test-case/3-fuzz-testing/mathy/prime-numbers/jsdoc.js new file mode 100644 index 00000000..e69de29b diff --git a/2-write-test-case/3-fuzz-testing/mysteries/mystery-1/fuzz.js b/2-write-test-case/3-fuzz-testing/mysteries/mystery-1/fuzz.js new file mode 100644 index 00000000..3339bf56 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mysteries/mystery-1/fuzz.js @@ -0,0 +1,38 @@ +/** + * Task description: Write a method that returns a duplicate-free array + * Expected Result: Duplicate-free array [1, 2, 3, 1, 2] => [1, 2, 3] + * Task Complexity: 2 of 5 + * + * @param {string[]} array - Array of primitive data types. + * @returns {Array} + * + * @author https://github.com/andrewborisov/javascript-practice/blob/master/arrays/solutions/06-unique.js + */ +export const solution = (array) => Array.from(new Set(array)); + +export const args = (chance) => { + const values = []; + const arraySize = Math.floor(Math.random() * 10); + for (let i = 0; i < arraySize; i++) { + const nextRando = chance.word(); + values.push(nextRando); + if (Math.random() < 0.3) { + values.push(nextRando); + } + if (Math.random() < 0.2) { + values.push(nextRando); + } + if (Math.random() < 0.1) { + values.push(nextRando); + } + } + + // https://medium.com/@nitinpatel_20236/how-to-shuffle-correctly-shuffle-an-array-in-javascript-15ea3f84bfb + for (let i = values.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * i); + const temp = values[i]; + values[i] = values[j]; + values[j] = temp; + } + return [values]; +}; diff --git a/2-write-test-case/3-fuzz-testing/mysteries/mystery-2/fuzz.js b/2-write-test-case/3-fuzz-testing/mysteries/mystery-2/fuzz.js new file mode 100644 index 00000000..b4fcf8a3 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mysteries/mystery-2/fuzz.js @@ -0,0 +1,31 @@ +/** + * Task description: Write a method that splits an array into parts of determined size + * Expected Result: ([1, 2, 3, 4, 5], 2) => [[1, 2], [3, 4], [5]] + * Task complexity: 3 of 5 + * @param {Array} array - An array of any elements + * @param {number} size - size of chunks + * @returns {Array} + * + * @author https://github.com/andrewborisov/javascript-practice/blob/master/arrays/solutions/09-chunk.js + */ +export const solution = (array, size) => { + const chunkedArr = []; + let index = 0; + while (index < array.length) { + chunkedArr.push(array.slice(index, size + index)); + index += size; + } + return chunkedArr; +}; + +export const args = (chance) => { + const values = []; + const arraySize = Math.floor(Math.random() * 20); + for (let i = 0; i < arraySize; i++) { + values.push(chance.word()); + } + + const chunkSize = chance.integer({ min: 1, max: 6 }); + + return [values, chunkSize]; +}; diff --git a/2-write-test-case/3-fuzz-testing/mysteries/mystery-3/fuzz.js b/2-write-test-case/3-fuzz-testing/mysteries/mystery-3/fuzz.js new file mode 100644 index 00000000..e36c72d0 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/mysteries/mystery-3/fuzz.js @@ -0,0 +1,49 @@ +/** + * Task description: Write a method that finds shallow intersections of objects + * Expected Result: ({ a: 1, b: 2 }, { c: 1, b: 2 }) => { b: 2 } + * @param {Object} firstObj - Object with values of primitive data types + * @param {Object} secondObj - Object with values of primitive data types + * @returns {Object} + * + * @author https://github.com/andrewborisov/javascript-practice/blob/master/objects/solutions/09-intersection.js + */ +export const solution = (firstObj, secondObj) => { + const firstObjKeys = Object.keys(firstObj); + + return firstObjKeys.reduce((acc = {}, key) => { + if (firstObj[key] === secondObj[key]) { + acc = { + ...acc, + [key]: firstObj[key], + }; + } + + return acc; + }, {}); +}; + +export const args = (chance) => { + const first = {}; + const second = {}; + + const maxKeys = Math.floor(Math.random() * 15); + for (let i = 0; i < maxKeys; i++) { + const key = chance.word(); + + const value = chance.integer({ min: -999, max: 999 }); + const rando = Math.random(); + if (rando < 0.2) { + first[key] = value; + } else if (rando < 0.4) { + second[key] = value; + } else if (rando < 0.8) { + first[key] = value; + second[key] = chance.integer({ min: -999, max: 999 }); + } else { + first[key] = value; + second[key] = value; + } + } + + return [first, second]; +}; diff --git a/2-write-test-case/3-fuzz-testing/study.json b/2-write-test-case/3-fuzz-testing/study.json new file mode 100644 index 00000000..29f5db4c --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/study.json @@ -0,0 +1,22 @@ +{ + "--defaults": { + "directory": "fuzz" + }, + "fuzz": { + "save": true, + "ask": false, + "variables": false, + "flowchart": false, + "blanks": false, + "openIn": false, + "table": "values", + "run": { + "type": "module", + "text": "fuzz" + } + }, + "study": { + "environment": true, + "type": "module" + } +} diff --git a/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/fuzz.js b/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/fuzz.js new file mode 100644 index 00000000..ebdab3e7 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/fuzz.js @@ -0,0 +1,12 @@ +export const solution = (arr) => { + return arr.filter((entry) => entry % 2 === 0); +}; + +export const args = (chance) => { + const randomNumbers = []; + const arraySize = Math.floor(Math.random() * 10); + for (let i = 0; i < arraySize; i++) { + randomNumbers.push(chance.integer({ min: -999999999, max: 999999999 })); + } + return [randomNumbers]; +}; diff --git a/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/jsdoc.js b/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/jsdoc.js new file mode 100644 index 00000000..0ab09dcb --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/jsdoc.js @@ -0,0 +1,7 @@ +/** + * removes all odd numbers + * throws an error if the array contains non-numbers + * does not modify the argument + * @param {number[]} + * @returns {number[]} + */ diff --git a/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/solutions/filter.js b/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/solutions/filter.js new file mode 100644 index 00000000..4b094b6d --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/even-numbers/solutions/filter.js @@ -0,0 +1 @@ +export const filter = (arr) => arr._(__); diff --git a/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/fuzz.js b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/fuzz.js new file mode 100644 index 00000000..9577f60a --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/fuzz.js @@ -0,0 +1,17 @@ +export const solution = (max = 0) => { + const result = []; + for (let i = 0; i < max; i++) { + if (i % 3 === 0 && i % 5 === 0) { + result.push('FizzBuzz'); + } else if (i % 3 === 0) { + result.push('Fizz'); + } else if (i % 5 === 0) { + result.push('Buzz'); + } else { + result.push(i); + } + } + return result; +}; + +export const args = (chance) => [chance.integer({ min: 0, max: 100 })]; diff --git a/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/jsdoc.js b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/jsdoc.js new file mode 100644 index 00000000..824df7ed --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/jsdoc.js @@ -0,0 +1,13 @@ +/** + * returns an array with "max" number of entries + * each index that's divisible by 3 and 5 stores "FizzBuzz" + * each index that's divisible by only 3 stores "Fizz" + * each index that's divisible by only 5 stores "Buzz" + * all other indexes store the same number as that index + * + * @param {number} max - how many numbers to check + * @returns {(number|string)[]} an array of length max + * all numbers not divisible by 3 or 5 remain + * all other numbers are replaced by the correct string + * @throws + */ diff --git a/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/many-functions.js b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/many-functions.js new file mode 100644 index 00000000..0a703f8d --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/many-functions.js @@ -0,0 +1,20 @@ +export const manyFunctions = (max = 0) => { + const threeDivides = (n) => _; + const fiveDivides = (n) => _; + const fifteenDivides = (n) => _; + + const FizzBuzzOrNumber = (num) => { + if (_) { + return 'FizzBuzz'; + } else if (_) { + return 'Fizz'; + } else if (_) { + return 'Buzz'; + } else { + return num; + } + }; + + // https://stackoverflow.com/a/33352604 + return [...Array(max).keys()].map(__); +}; diff --git a/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/so-short.js b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/so-short.js new file mode 100644 index 00000000..be8a2ed5 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/so-short.js @@ -0,0 +1,8 @@ +export const soShort = (max = 0) => { + const result = []; + for (let i = 0; i < _; ) + result._((++i % _ ? '' : '_') + (i % _ ? '' : '_') || i); + return result; + + // https://codeburst.io/javascript-breaking-down-the-shortest-possible-fizzbuzz-answer-94a0ad9d128a +}; diff --git a/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/staircase.js b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/staircase.js new file mode 100644 index 00000000..9a342334 --- /dev/null +++ b/2-write-test-case/3-fuzz-testing/unclassified/fizz-buzz/solutions/staircase.js @@ -0,0 +1,8 @@ +export const staircase = (max = 0) => { + let countUp = _; + const result = []; + while (++countUp < max) { + result.push(countUp % 15 === 0 ? "_" : _ ? "Buzz" : _ ? "Fizz" : _); + } + return result; +}; diff --git a/2-write-test-case/4-test-driven-development/README.md b/2-write-test-case/4-test-driven-development/README.md new file mode 100644 index 00000000..1f6acf64 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/README.md @@ -0,0 +1,31 @@ +# Test Driven Development + +> it's easiest to study these in VSCode and run the tests from terminal. + +Write tests first, then the function. + +The exercises in this folder are open-ended. All you get is a JSDoc describing what you should write. [TDD is a cycle](https://www.youtube.com/watch?v=m43Ma_8TPH0), it's not a one-way street. You should run your tests very often and you can always fix your tests when they are broken, or change them as you understand the problem better. + +Careful! Sometimes your function will be right, but the tests will be wrong ;) + +1. **Red**: Run the tests and see which ones fail. Failing tests are not a personal failure, they're helpful information! + By carefully noting which tests pass and which ones fail you can build an understanding of what you need to fix in your code. +2. **Green**: Pick a specific failing test that you want to pass, only one! Working in small increments is the safest way to go. Make a small change or add a tiny bit of code and run the tests again. + - You might find it helpful to create a new function instead of constantly changing the old one. + You can start from scratch by copy-pasting the stub or you can copy-paste your last attempt and modify it. + This way you can easily return to and old version, study the ideas you've tried so far, + or copy-paste snippets from old functions into your newest attempt. + - The `function-design` exercises are written so it's (relatively) easy to have many solutions side-by-side. You can comment and uncomment different solutions in the testing loop to compare their behavior side-by-side. +3. **Refactor**: After passing your chosen test, take a step back and revaluate your code. + Make some changes so it's easier to read and understand (but don't try to pass any new tests!). + - Add some comments above your latest solution describing what worked and what didn't. + - Consider adding more tests: does your code have a bug that wasn't caught? did you find a new use-case that wasn't covered? + - Use comments in your function to point out important steps in your implementation. + - Change variable names to be make it more clear what role they play and how they are used. + - At this step in the workflow your file may get long and messy. To keep things easy to read, you can collapse functions you're not working on, and delete ones that you're finished learning from. + - After revising your code, take a second to revise your strategy. Maybe you notice that the way you wanted to solve the problem isn't possible! +4. Go to step 1 (repeat!) + +--- + +> PS. Exercises in this folder are adapted from [rradfar/javascript-coding-challenges](https://github.com/rradfar/javascript-coding-challenges) diff --git a/2-write-test-case/4-test-driven-development/exercises/count-characters.js b/2-write-test-case/4-test-driven-development/exercises/count-characters.js new file mode 100644 index 00000000..6e8d6d1c --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/count-characters.js @@ -0,0 +1,16 @@ +/** + * Counts how many times each character appears in a string. + * + * @param {string} [text=''] - The string of characters to count. + * @returns {number{}} An object of key/value pairs counting each character. + * + * @example + * + * countCharacters('hi'); // { h: 1, i: 1 } + * + * @example + * + * countCharacters('hiiii'); // { h: 1, i: 4 } + * + */ +export const countCharacters = (text = '') => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/count-characters.spec.js b/2-write-test-case/4-test-driven-development/exercises/count-characters.spec.js new file mode 100644 index 00000000..d4e8273c --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/count-characters.spec.js @@ -0,0 +1,3 @@ +import { countCharacters } from './count-characters.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/diff-arrays.js b/2-write-test-case/4-test-driven-development/exercises/diff-arrays.js new file mode 100644 index 00000000..0ebbfbf2 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/diff-arrays.js @@ -0,0 +1,24 @@ +/** + * Returns an array containing only the items that are not in both a and b. + * This is a pure function with no side-effects. + * + * @param {number[]} [a=[]] - One of the two arrays. + * @param {number[]} [b=[]] - The other array. + * @returns {number[]} A new array with items not in both a and b. + * + * @example + * + * diffArrays([2], [1, 3]); // [3] + * + * @example + * + * diffArrays([2, NaN], [3, 1]); // [NaN, 3] + * + * @example + * + * diffArrays([2, 1], [3, 2]); // [] + * @example + * + * diffArrays([1, 2, 3], [4, 5]); // [1, 2, 3, 4, 5] + */ +export const diffArrays = (a = [], b = []) => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/diff-arrays.spec.js b/2-write-test-case/4-test-driven-development/exercises/diff-arrays.spec.js new file mode 100644 index 00000000..b73488a0 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/diff-arrays.spec.js @@ -0,0 +1,3 @@ +import { diffArrays } from './diff-arrays.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/even-or-odd.js b/2-write-test-case/4-test-driven-development/exercises/even-or-odd.js new file mode 100644 index 00000000..64818e2c --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/even-or-odd.js @@ -0,0 +1,13 @@ +/** + * Determines if each number in an array is even or odd. + * This is a pure function with no side-effects. + * + * @param {number[]} [nums=[]] - An array of numbers. + * @returns {string[]} - An array with the same number of entries as nums. + * Each number has been replaced with "even" or "odd". + * + * @example + * + * evenOrOdd([1, 2]); // ['odd', 'even'] + */ +export const evenOrOdd = (nums = []) => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/even-or-odd.spec.js b/2-write-test-case/4-test-driven-development/exercises/even-or-odd.spec.js new file mode 100644 index 00000000..7436dfa7 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/even-or-odd.spec.js @@ -0,0 +1,3 @@ +import { evenOrOdd } from './even-or-odd.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/matching-braces.js b/2-write-test-case/4-test-driven-development/exercises/matching-braces.js new file mode 100644 index 00000000..ae5fb428 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/matching-braces.js @@ -0,0 +1,32 @@ +/** + * Checks if the braces in a string are matched. + * + * @param {string} [text=''] - The text of braces to check. + * @returns {boolean} Whether or not the braces are matched. + * + * @example + * + * matchingBraces('['); // false + * + * @example + * + * matchingBraces('()'); // true + * + * @example + * + * matchingBraces('(]'); // false + * + * @example + * + * matchingBraces('{[]}'); // true + * + * @example + * + * matchingBraces('([)]'); // false + * + * @example + * + * matchingBraces('()[]{}'); // true + * + */ +export const matchingBraces = (text = '') => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/matching-braces.spec.js b/2-write-test-case/4-test-driven-development/exercises/matching-braces.spec.js new file mode 100644 index 00000000..9a4a560c --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/matching-braces.spec.js @@ -0,0 +1,3 @@ +import { matchingBraces } from './matching-braces.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/middle-character.js b/2-write-test-case/4-test-driven-development/exercises/middle-character.js new file mode 100644 index 00000000..676bb55b --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/middle-character.js @@ -0,0 +1,8 @@ +/** + * Returns the middle character from a string. + * It returns the middle 2 characters if the string has an even length. + * + * @param {string} [text=''] - Find the middle character(s) of this string. + * @returns {string} The middle character(s) in the text. + */ +export const middleCharacter = (text = '') => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/middle-character.spec.js b/2-write-test-case/4-test-driven-development/exercises/middle-character.spec.js new file mode 100644 index 00000000..ae0bd510 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/middle-character.spec.js @@ -0,0 +1,3 @@ +import { middleCharacter } from './middle-character.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/reverse-words.js b/2-write-test-case/4-test-driven-development/exercises/reverse-words.js new file mode 100644 index 00000000..24e46306 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/reverse-words.js @@ -0,0 +1,15 @@ +/** + * Reverses each word in a string. + * + * @param {string} [text=''] - The string of words to reverse. + * @returns {string} The text, with each word reversed. + * + * @example + * + * reverseWords('hello'); // 'olleh' + * + * @example + * + * reverseWords('hello world!'); // 'olleh dlrow!' + */ +export const reverseWords = (text = '') => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/reverse-words.spec.js b/2-write-test-case/4-test-driven-development/exercises/reverse-words.spec.js new file mode 100644 index 00000000..ef62df4e --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/reverse-words.spec.js @@ -0,0 +1,3 @@ +import { reverseWords } from './reverse-words.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/sort-digits.js b/2-write-test-case/4-test-driven-development/exercises/sort-digits.js new file mode 100644 index 00000000..f7c2991e --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/sort-digits.js @@ -0,0 +1,21 @@ +/** + * Creates a new number by sorting the digits in another. + * It will sort the digits up or down, depending on a flag parameter. + * + * @param {number} [toSort=0] - The number who's digits need sorting. + * @param {boolean} [up=true] - Whether to sort the digits up or down. + * @return {number} A number with the same digits, but sorted. + * + * @example + * + * sortDigits(2321); // 1223 + * + * @example + * + * sortDigits(2321, true); // 1223 + * + * @example + * + * sortDigits(2321, false); // 3221 + */ +export const sortDigits = (toSort = 0, up = true) => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/sort-digits.spec.js b/2-write-test-case/4-test-driven-development/exercises/sort-digits.spec.js new file mode 100644 index 00000000..89887b3f --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/sort-digits.spec.js @@ -0,0 +1,3 @@ +import { sortDigits } from './sort-digits.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/time-in-milliseconds.js b/2-write-test-case/4-test-driven-development/exercises/time-in-milliseconds.js new file mode 100644 index 00000000..1272a78c --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/time-in-milliseconds.js @@ -0,0 +1,21 @@ +/** + * Converts the time from hours/minutes/second to milliseconds. + * + * @param {number} [h=0] - An integer between 0 and 23. + * @param {number} [m=0] - An integer between 0 and 59. + * @param {number} [s=0] - An integer between 0 and 59. + * @returns {number} - The number of milliseconds since midnight. + * + * @example + * + * timeInMilliseconds(0, 0, 0); // 0 + * + * @example + * + * timeInMilliseconds(0, 0, 1); // 1000 + * + * @example + * + * timeInMilliseconds(0, 1, 1); // 61000 + */ +export const timeInMilliseconds = (h = 0, m = 0, s = 0) => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/time-in-milliseconds.spec.js b/2-write-test-case/4-test-driven-development/exercises/time-in-milliseconds.spec.js new file mode 100644 index 00000000..5f0d81dd --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/time-in-milliseconds.spec.js @@ -0,0 +1,3 @@ +import { timeInMilliseconds } from './time-in-milliseconds.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/exercises/unique-entries.js b/2-write-test-case/4-test-driven-development/exercises/unique-entries.js new file mode 100644 index 00000000..9fdcca72 --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/unique-entries.js @@ -0,0 +1,21 @@ +/** + * Finds all the unique numbers in an array and sorts them. + * This is a pure function with no side-effects. + * + * @param {number[]} [nums=[]] - The numbers to check. + * @param {boolean} [up=true] - Wether to sort the entries up or down. + * @returns {number[]} The unique entries, in order. + * + * @example + * + * uniqueEntries([3, 1, 3, 2]); // [1, 2, 3] + * + * @example + * + * uniqueEntries([3, 1, 3, 2], true); // [1, 2, 3] + * + * @example + * + * uniqueEntries([3, 1, 3, 2], false); // [3, 2, 1] + */ +export const uniqueEntries = (nums = [], up = true) => {}; diff --git a/2-write-test-case/4-test-driven-development/exercises/unique-entries.spec.js b/2-write-test-case/4-test-driven-development/exercises/unique-entries.spec.js new file mode 100644 index 00000000..851b159c --- /dev/null +++ b/2-write-test-case/4-test-driven-development/exercises/unique-entries.spec.js @@ -0,0 +1,3 @@ +import { uniqueEntries } from './unique-entries.js'; + +describe('', () => {}); diff --git a/2-write-test-case/4-test-driven-development/study.json b/2-write-test-case/4-test-driven-development/study.json new file mode 100644 index 00000000..a73ec7ea --- /dev/null +++ b/2-write-test-case/4-test-driven-development/study.json @@ -0,0 +1,9 @@ +{ + "study": { + "type": "module", + "environment": true, + "trace": false, + "variables": false, + "loopGuard": false + } +} diff --git a/2-write-test-case/z-code-golf.md b/2-write-test-case/z-code-golf.md new file mode 100644 index 00000000..009338ad --- /dev/null +++ b/2-write-test-case/z-code-golf.md @@ -0,0 +1,23 @@ +# 🐔 Code Golf + +> "This seems to serve dual purposes. 1. Tips on how to minify code, and 2. A list of prohibited code styles for your team." +> +> - [spacejack2114](https://www.reddit.com/r/programming/comments/kzx3te/comment/gjqyac0/?utm_source=share&utm_medium=web2x&context=3) + +In golf you want to have the fewest points possible. In code golf you want to write the shortest function possible, just for fun. If you're already comfortable with TDD, then code golf can be a fun way to push your understanding of JS. + +Go back to some of your favorite exercises in this chapter and try refactoring your solutions to be as short as humanly possible. Don't forget to run your tests every time you make a change! + +_PS. Almost everything you'll write in code golf is a bad practice that will not pass linter checks or code review._ + +--- + +## References + +- **Watch:** + - [CodinGame](https://www.youtube.com/watch?v=F-xofAKUnHY) +- **Read:** + - [emnudge](https://dev.to/emnudge/js-code-golfing-how-to-ruin-everyone-s-day-40h3) + - [geeksforgeeks](https://www.geeksforgeeks.org/code-golfing-in-javascript/) + - [statox](https://www.statox.fr/posts/2021/05/javascript_golf_tips/) + - stackexchange: [1](https://codegolf.stackexchange.com/questions/2682/tips-for-golfing-in-javascript), [2](https://codegolf.stackexchange.com/questions/37624/tips-for-golfing-in-ecmascript-6-and-above) diff --git a/2-write/1-function-design/exercises/easy/count-down.test.js b/2-write/1-function-design/exercises/easy/count-down.test.js index 98e9d0f1..b1e3fae8 100644 --- a/2-write/1-function-design/exercises/easy/count-down.test.js +++ b/2-write/1-function-design/exercises/easy/count-down.test.js @@ -10,20 +10,19 @@ */ // -------- your solutions -------- - +import {countdown} from './countDown.test.js' for (const solution of [secretSolution]) { // the main test suite for the function - describe(solution.name + ': counts down to 0', () => { - it('default parameter is 0 -> [0]', () => { + //describe(solution.name + ': counts down to 0', () => { + //it('default parameter is 0 -> [0]', () => { expect(solution()).toEqual([0]); }); it('0 -> [0]', () => { expect(solution(0)).toEqual([0]); }); - it('1 -> [1, 0]', () => { - expect(solution(1)).toEqual([1, 0]); - }); - // write at least 5 more tests ... + + it('0 <- [0]', () => { + expect(solution(0)).toEqual([0]) }); } diff --git a/2-write/1-function-design/exercises/easy/count-up.test.js b/2-write/1-function-design/exercises/easy/count-up.test.js index c2f15861..9c9036fb 100644 --- a/2-write/1-function-design/exercises/easy/count-up.test.js +++ b/2-write/1-function-design/exercises/easy/count-up.test.js @@ -1,9 +1,9 @@ -// #todo +/* eslint-disable spellcheck/spell-checker */ +/* eslint-disable linebreak-style */ -'use strict'; - -/** - * builds an array counting up from 0 to `max` +/** .................... + * Builds an array counting up from 0 to `max` + * * @param {number} [max=0] - the number to count up to * max must be an integer that is greater than 0 * @returns {number[]} an array of all numbers from 0 to `max` @@ -11,9 +11,27 @@ // -------- your solutions -------- -for (const solution of [secretSolution]) { +import { countUp } from "./countUp.test"; + +/** + * @param max + */ +/* function countUp(max = 0) { + if (!Number.isInteger(max) || max < 0) { + throw new Error('max must be a non-negative integer.'); + } + + const result = []; + for (let i = 0; i <= max; i++) { + result.push(i); + } + return result; +} */ +// const countup = () +// eslint-disable-next-line no-restricted-syntax +for (const solution of [countUp]) { // the main test suite for the function - describe(solution.name + ': counts up from 0', () => { + describe(`${solution.name}: counts up from 0`, () => { it('default parameter is 0 -> [0]', () => { const actual = solution(); expect(actual).toEqual([0]); @@ -24,10 +42,31 @@ for (const solution of [secretSolution]) { it('1 -> [0, 1]', () => { expect(solution(1)).toEqual([0, 1]); }); - // write at least 5 more tests ... + it('2 -> [0, 1, 3, 4, 6]', () => { + expect(solution(2)).toEqual([0, 1, 3, 4, 6]); + it('3 -> [0, 1, 2, 3, 4, 6, 7, 9]', () => { + expect(solution(3)).toEqual([0, 1, 2, 4, 6, 7, 9]); + }); + + '2 -> [0, 1, 3, 4, 6]', () => { + expect(solution(2)).toEqual([0, 1, 3, 4, 6]); + + // Test case for input 3 (modified to fail) + it('3 -> [0, 1, 3, 4, 6, 7, 9]', () => { + expect(solution(3)).toEqual([0, 1, 3, 4, 6, 7, 8]); // Expected output modified to [0, 1, 3, 4, 6, 7, 8] + }); + }; + }); + }); + + it('3 -> [0, 1, 2, 3, 4, 5]', () => { + expect(solution(3)).toEqual([0, 1, 2, 3, 4, 5]); }); } // minified solution for testing your tests // prettier-ignore -function secretSolution(a = 0) { if ("number" != typeof a) throw new TypeError("max is not a number"); if (!Number.isInteger(a)) throw new Error("max is not an integer"); if (0 > a) throw new RangeError("max is less than 0"); const b = []; for (let c = 0; c <= a; c++)b.push(c); return b } +/** + * @param a + */ +function secretSolution(a = 0) { if (typeof a !== "number") throw new TypeError("max is not a number"); if (!Number.isInteger(a)) throw new Error("max is not an integer"); if (a < 0) throw new RangeError("max is less than 0"); const b = []; for (let c = 0; c <= a; c++)b.push(c); return b; } diff --git a/2-write/1-function-design/exercises/easy/countDown.test.js b/2-write/1-function-design/exercises/easy/countDown.test.js new file mode 100644 index 00000000..bf5267fe --- /dev/null +++ b/2-write/1-function-design/exercises/easy/countDown.test.js @@ -0,0 +1,17 @@ +export {conutDown} from './count-down.test' + + + +function const = ( start = 0) => { + if ( start <= 0 ) { + return[0] + } + else{ + let solutionArray = [] + for ( let i=start; i>= ;i--){ + solutionArray.push(i) + + }; + return solutionArray + } +} \ No newline at end of file diff --git a/2-write/1-function-design/exercises/easy/countUp.test.js b/2-write/1-function-design/exercises/easy/countUp.test.js new file mode 100644 index 00000000..86a2866f --- /dev/null +++ b/2-write/1-function-design/exercises/easy/countUp.test.js @@ -0,0 +1,14 @@ +export {countUp} from './count-up.test' + +function countUp(max = 0) { + if (!Number.isInteger(max) || max < 0) { + throw new Error('max must be a non-negative integer.'); + } + + const result = []; + for (let i = 0; i <= max; i++) { + result.push(i); + } + return result; + } + diff --git a/2-write/1-function-design/exercises/easy/reverse-a-string.test.js b/2-write/1-function-design/exercises/easy/reverse-a-string.test.js index f74b0414..755b1bfd 100644 --- a/2-write/1-function-design/exercises/easy/reverse-a-string.test.js +++ b/2-write/1-function-design/exercises/easy/reverse-a-string.test.js @@ -22,7 +22,12 @@ for (const solution of [secretSolution]) { it('a string with all capital letters', () => { expect(solution('ASDF')).toEqual('FDSA'); }); - // write at least 5 more tests ... + it('a string with number', () => { + expect(solution('12345').toEqual('54321')); + }); + it('a string with whitespace', () => { + expect(solution(' Hello, World! ')).toEqual(' !dlroW, olleH '); + }); }); }