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.