diff --git a/.eslintrc.json b/.eslintrc.json index d5624ef4..c02a73a5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,14 +1,13 @@ { - "env": { - "commonjs": true, - "es2021": true, - "node": true, - "jest": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 12 - }, - "rules": { - } + "env": { + "commonjs": true, + "es2021": true, + "node": true, + "jest": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 12 + }, + "rules": {} } diff --git a/2021-07-21-17-19-02.png b/2021-07-21-17-19-02.png new file mode 100644 index 00000000..490eb6e2 Binary files /dev/null and b/2021-07-21-17-19-02.png differ diff --git a/README.md b/README.md index 5b1f6419..d3ef04cc 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ There are two possible ways to submit your project. Your instructor should have ### Task 2: Minimum Viable Product - [ ] For Exercises 1-7 inside `index.js`: + - [ ] Write the tests in `index.test.js`. - [ ] Implement the function or the class in `index.js`. - - [ ] Write the corresponding tests in `index.test.js`. #### Notes @@ -35,7 +35,6 @@ There are two possible ways to submit your project. Your instructor should have - Run tests locally with Jest executing `npm test`. - You can add console.logs to `index.js` to manually test your code. (e.g. `console.log(car.drive(10));`). - The output of your log statements can be found in the terminal you run `npm run dev` in. -- You must remove the `.todo` from the tests in order for them to execute. #### Hot Tips @@ -44,3 +43,48 @@ There are two possible ways to submit your project. Your instructor should have - In your solution, it is essential that you follow best practices and produce clean and professional results. - Schedule time to review, refine, and assess your work. - Perform basic professional polishing including spell-checking and grammar-checking on your work. + +# Result: + +``` + + PASS ./index.test.js (16.312 s) + [Exercise 1] trimProperties + ✓ [1] returns an object with the properties trimmed (7 ms) + ✓ [2] returns a copy, leaving the original object intact (1 ms) + [Exercise 2] trimPropertiesMutation + ✓ [3] returns an object with the properties trimmed (1 ms) + ✓ [4] the object returned is the exact same one we passed in (1 ms) + [Exercise 3] findLargestInteger + ✓ [5] returns the largest number in an array of objects { integer: 2 } + [Exercise 4] Counter + ✓ [6] the FIRST CALL of counter.countDown returns the initial count (1 ms) + ✓ [7] the SECOND CALL of counter.countDown returns the initial count minus one + ✓ [8] the count eventually reaches zero but does not go below zero (1 ms) + [Exercise 5] Seasons + ✓ [9] the FIRST call of seasons.next returns "summer" (1 ms) + ✓ [10] the SECOND call of seasons.next returns "fall" + ✓ [11] the THIRD call of seasons.next returns "winter" (1 ms) + ✓ [12] the FOURTH call of seasons.next returns "spring" + ✓ [13] the FIFTH call of seasons.next returns again "summer" + ✓ [14] the 40th call of seasons.next returns "spring" + [Exercise 6] Car + ✓ [15] driving the car returns the updated odometer (1 ms) + ✓ [16] driving the car uses gas + ✓ [17] refueling allows to keep driving (1 ms) + ✓ [18] adding fuel to a full tank has no effect + [Exercise 7] isEvenNumberAsync + ✓ [19] resolves true if passed an even number (1 ms) + ✓ [20] resolves false if passed an odd number + +Test Suites: 1 passed, 1 total +Tests: 20 passed, 20 total +Snapshots: 0 total +Time: 17.693 s +Ran all test suites. + + + +``` + + diff --git a/index.js b/index.js index 28090f12..e3d5bd3e 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,11 @@ */ function trimProperties(obj) { // ✨ implement + const trimmed = {}; + for (const prop in obj) { + trimmed[prop] = obj[prop].trim(); + } + return trimmed; } /** @@ -19,6 +24,10 @@ function trimProperties(obj) { * trimPropertiesMutation({ name: ' jane ' }) // returns the object mutated in place { name: 'jane' } */ function trimPropertiesMutation(obj) { + for (const prop in obj) { + obj[prop] = obj[prop].trim(); + } + return obj; // ✨ implement } @@ -32,6 +41,7 @@ function trimPropertiesMutation(obj) { */ function findLargestInteger(integers) { // ✨ implement + return Math.max(...integers); } class Counter { @@ -41,6 +51,8 @@ class Counter { */ constructor(initialNumber) { // ✨ initialize whatever properties are needed + this.count = initialNumber; + this.initialized = false; } /** @@ -56,7 +68,11 @@ class Counter { * counter.countDown() // returns 0 */ countDown() { - // ✨ implement + if (!this.initialized) { + this.initialized = true; + return this.count; + } + return this.count > 0 ? --this.count : this.count; } } @@ -66,6 +82,8 @@ class Seasons { */ constructor() { // ✨ initialize whatever properties are needed + this.seasons = ["summer", "fall", "winter", "spring"]; + this.season = "spring"; } /** @@ -82,6 +100,12 @@ class Seasons { */ next() { // ✨ implement + if (this.season === "spring") { + this.season = this.seasons[0]; + } else { + this.season = this.seasons[this.seasons.indexOf(this.season) + 1]; + } + return this.season; } } @@ -93,14 +117,17 @@ class Car { * @param {number} mpg - miles the car can drive per gallon of gas */ constructor(name, tankSize, mpg) { - this.odometer = 0 // car initilizes with zero miles - this.tank = tankSize // car initiazes full of gas + this.odometer = 0; // car initilizes with zero miles + this.tank = tankSize; // car initiazes full of gas // ✨ initialize whatever other properties are needed + this.tankSize = tankSize; + this.name = name; + this.mpg = mpg; } /** * [Exercise 6B] Car.prototype.drive adds miles to the odometer and consumes fuel according to mpg - * @param {string} distance - the distance we want the car to drive + * @param {number} distance - the distance we want the car to drive * @returns {number} - the updated odometer value * * EXAMPLE @@ -112,7 +139,16 @@ class Car { * focus.drive(200) // returns 600 (ran out of gas after 100 miles) */ drive(distance) { - // ✨ implement + while (distance !== 0) { + if (0 <= this.tank * this.mpg) { + distance -= 1; + this.odometer += 1; + this.tank -= 1 / this.mpg; + } else { + distance = 0; + } + } + return this.odometer; } /** @@ -127,7 +163,8 @@ class Car { * focus.refuel(99) // returns 600 (tank only holds 20) */ refuel(gallons) { - // ✨ implement + this.tank = clamp(this.tank + gallons, 0, this.tankSize); + return this.mpg * this.tank; } } @@ -143,17 +180,17 @@ class Car { * isEvenNumberAsync(3).then(result => { * // result is false * }) - * isEvenNumberAsync('foo').catch(error => { - * // error.message is "number must be a number" - * }) - * isEvenNumberAsync(NaN).catch(error => { - * // error.message is "number must be a number" - * }) */ function isEvenNumberAsync(number) { - // ✨ implement + return new Promise((res) => { + res(number % 2 === 0 ? true : false); + }); } +const clamp = (num, min, max) => { + return Math.min(Math.max(num, min), max); +}; + module.exports = { trimProperties, trimPropertiesMutation, @@ -162,4 +199,4 @@ module.exports = { Counter, Seasons, Car, -} +}; diff --git a/index.test.js b/index.test.js index cfb0a0b3..7b2968df 100644 --- a/index.test.js +++ b/index.test.js @@ -1,60 +1,144 @@ -const utils = require('./index') +const utils = require("./index"); -describe('[Exercise 1] trimProperties', () => { - test('[1] returns an object with the properties trimmed', () => { +describe("[Exercise 1] trimProperties", () => { + test("[1] returns an object with the properties trimmed", () => { // EXAMPLE - const input = { foo: ' foo ', bar: 'bar ', baz: ' baz' } - const expected = { foo: 'foo', bar: 'bar', baz: 'baz' } - const actual = utils.trimProperties(input) - expect(actual).toEqual(expected) - }) - // test('[2] returns a copy, leaving the original object intact', () => {}) -}) + const input = { foo: " foo ", bar: "bar ", baz: " baz" }; + const expected = { foo: "foo", bar: "bar", baz: "baz" }; + const actual = utils.trimProperties(input); + expect(actual).toEqual(expected); + }); + test("[2] returns a copy, leaving the original object intact", () => { + const input = { foo: " foo ", bar: "bar ", baz: " baz" }; + utils.trimProperties(input); + expect(input).toEqual({ foo: " foo ", bar: "bar ", baz: " baz" }); + }); +}); -describe('[Exercise 2] trimPropertiesMutation', () => { - // test('[3] returns an object with the properties trimmed', () => {}) - // test('[4] the object returned is the exact same one we passed in', () => {}) -}) +describe("[Exercise 2] trimPropertiesMutation", () => { + test("[3] returns an object with the properties trimmed", () => { + // EXAMPLE + const input = { foo: " foo ", bar: "bar ", baz: " baz" }; + const expected = { foo: "foo", bar: "bar", baz: "baz" }; + const actual = utils.trimPropertiesMutation(input); + expect(actual).toEqual(expected); + }); + test("[4] the object returned is the exact same one we passed in", () => { + const input = { foo: " foo ", bar: "bar ", baz: " baz" }; + const result = utils.trimPropertiesMutation(input); + expect(input).toEqual(result); + }); +}); -describe('[Exercise 3] findLargestInteger', () => { - // test('[5] returns the largest number in an array of objects { integer: 2 }', () => {}) -}) +describe("[Exercise 3] findLargestInteger", () => { + test("[5] returns the largest number in an array of objects { integer: 2 }", () => { + const input = [1, 2, 3, 4, 5, 6, 7, 8, 900, 10, 12, 13, 15, 20, 155]; + const result = utils.findLargestInteger(input); + expect(result).toEqual(Math.max(...input)); + }); +}); -describe('[Exercise 4] Counter', () => { - let counter +describe("[Exercise 4] Counter", () => { + let counter; beforeEach(() => { - counter = new utils.Counter(3) // each test must start with a fresh couter - }) - // test('[6] the FIRST CALL of counter.countDown returns the initial count', () => {}) - // test('[7] the SECOND CALL of counter.countDown returns the initial count minus one', () => {}) - // test('[8] the count eventually reaches zero but does not go below zero', () => {}) -}) + counter = new utils.Counter(3); // each test must start with a fresh couter + }); + test("[6] the FIRST CALL of counter.countDown returns the initial count", () => { + const result = counter.countDown(); + expect(result).toBe(3); + }); + test("[7] the SECOND CALL of counter.countDown returns the initial count minus one", () => { + counter.countDown(); + const result = counter.countDown(); + expect(result).toBe(2); + }); + test("[8] the count eventually reaches zero but does not go below zero", () => { + counter.countDown(); + counter.countDown(); + counter.countDown(); + let count = counter.countDown(); + expect(count).toBe(0); + counter.countDown(); + counter.countDown(); + counter.countDown(); + count = counter.countDown(); + expect(count).toBe(0); + }, 200); +}); -describe('[Exercise 5] Seasons', () => { - let seasons +describe("[Exercise 5] Seasons", () => { + let seasons; beforeEach(() => { - seasons = new utils.Seasons() // each test must start with fresh seasons - }) - // test('[9] the FIRST call of seasons.next returns "summer"', () => {}) - // test('[10] the SECOND call of seasons.next returns "fall"', () => {}) - // test('[11] the THIRD call of seasons.next returns "winter"', () => {}) - // test('[12] the FOURTH call of seasons.next returns "spring"', () => {}) - // test('[13] the FIFTH call of seasons.next returns again "summer"', () => {}) - // test('[14] the 40th call of seasons.next returns "spring"', () => {}) -}) + seasons = new utils.Seasons(); // each test must start with fresh seasons + }); + test('[9] the FIRST call of seasons.next returns "summer"', () => { + expect(seasons.next()).toBe("summer"); + }); + test('[10] the SECOND call of seasons.next returns "fall"', () => { + seasons.next(); + expect(seasons.next()).toBe("fall"); + }); + test('[11] the THIRD call of seasons.next returns "winter"', () => { + for (let i = 0; i < 2; i++) { + seasons.next(); + } + expect(seasons.next()).toBe("winter"); + }); + test('[12] the FOURTH call of seasons.next returns "spring"', () => { + for (let i = 0; i < 3; i++) { + seasons.next(); + } + expect(seasons.next()).toBe("spring"); + }); + test('[13] the FIFTH call of seasons.next returns again "summer"', () => { + for (let i = 0; i < 4; i++) { + seasons.next(); + } + expect(seasons.next()).toBe("summer"); + }); + test('[14] the 40th call of seasons.next returns "spring"', () => { + for (let i = 0; i < 39; i++) { + seasons.next(); + } + expect(seasons.next()).toBe("spring"); + }); +}); -describe('[Exercise 6] Car', () => { - let focus +describe("[Exercise 6] Car", () => { + let focus; beforeEach(() => { - focus = new utils.Car('focus', 20, 30) // each test must start with a fresh car - }) - // test('[15] driving the car returns the updated odometer', () => {}) - // test('[16] driving the car uses gas', () => {}) - // test('[17] refueling allows to keep driving', () => {}) - // test('[18] adding fuel to a full tank has no effect', () => {}) -}) + focus = new utils.Car("focus", 20, 30); // each test must start with a fresh car + }); + test("[15] driving the car returns the updated odometer", () => { + let odometer = focus.drive(30); + expect(odometer).toBe(30); + odometer = focus.drive(5); + expect(odometer).toBe(35); + }); + test("[16] driving the car uses gas", () => { + let originalGas = focus.tank; + focus.drive(5); + expect(originalGas).not.toBe(focus.tank); + }); + test("[17] refueling allows to keep driving", () => { + let odometer = focus.drive(800); + expect(odometer).toBe(600); + focus.refuel(20); + odometer = focus.drive(600); + expect(odometer).toBe(1200); + }); + test("[18] adding fuel to a full tank has no effect", () => { + const oriTank = focus.tank; + focus.refuel(3000); + expect(oriTank).toBe(focus.tank); + }); +}); -describe('[Exercise 7] isEvenNumberAsync', () => { - // test('[19] resolves true if passed an even number', () => {}) - // test('[20] resolves false if passed an odd number', () => {}) -}) +describe("[Exercise 7] isEvenNumberAsync", () => { + test("[19] resolves true if passed an even number", async () => { + expect(await utils.isEvenNumberAsync(2)).toBe(true); + }); + test("[20] resolves false if passed an odd number", async () => { + expect(await utils.isEvenNumberAsync(1)).toBe(false); + }); +}); diff --git a/jest-tdd.html b/jest-tdd.html new file mode 100644 index 00000000..b0a75d76 --- /dev/null +++ b/jest-tdd.html @@ -0,0 +1,1109 @@ + + +
++ After a few years of experience developing on my own personal projects, + I recently decided to become a Full-Stack developer. +
++ This new situation encouraged me to start thinking about practices that + I’ve neglected until now, such as testing my code. +
++ That is why I wanted to start my journey through Test Driven + Development. I’ve decided to share my first steps here with you. +
++ I decided to start with the first Osherove TDD kata. You can access the + full exercise here. +
+
+ The goal is to deliver a function that takes a string entry ("1, 2, 3"
+ for instance) and returns the sum of all numbers.
+
Our project will have the following structure:
++js-kata-jest/ +├─ src/ + └─ kata.js +├─ test/ + └─ kata.test.js +└─ package.json ++
+ First we have to set up the test environment. As a React developer, I + decided to go with Jest. + You may use any other testing library of your choice. +
++yarn add --dev jest ++
or with npm:
++npm install --save-dev jest ++
+ I am using Atom as a code editor, and installed the
+ tester-jest package.
+ This allowed me to run my tests on save for any
+ *.test.js file.
+
The theory behind TDD is quite simple, and revolves around 3 steps:
++ In the next part, we are going to go into detail for each of these + steps. +
+
+ First, we want to handle the case where our add function is
+ given an empty string or one with a single element.
+
2. Writing the code
+if statement that handles the parsing
+ of a single provided element
+ Here is the final code:
+3. Refactoring the code
++ As it is our first functionality, we can skip this step for now — + but we will soon return to it. 😉 +
++ We will now handle the case where the string contains multiple elements: +
++ The new test makes sure that calculation of a multiple element string + was done correctly: +
+2. Writing the code
+if statement on purpose to be sure
+ that our first two tests affect the new solution
+ , as a separator
+ Here is the final code:
+3. Refactoring the code
+As we can see above, there are several problems within our code:
+; separator, for instance.
+ if statement.
+ We could reverse the logic to extract our main code from it.
+
+ So we can add a new separator variable, which will decide
+ on the separator type. We can also merge the two
+ if statement into one, and then reverse the logic.
+
We can now run our test again before moving on to the next loop.
++ We can now handle the declaration of new separators and avoid the entry + of negative numbers. +
+2. Writing the code
+separator string by a
+ separators array, where we add the \n value.
+ negatives, that will spot negative
+ values within the entry.
+ negatives array isn’t empty, throw an
+ error.
+ Here is the final code:
+3. Refactoring the code
+We now have two new possible optimizations:
+const regexp.
+ parseInt(list[i])several times, so we should
+ store the value only once to speed up the for loop.
+ + We can now run our tests again to make sure that all our expected + functionalities are still working. +
+