├── .github ├── stale.yml └── workflows │ └── test.yml ├── .gitignore ├── index.html ├── javascript ├── chronometer.js └── index.js ├── package.json ├── readme.md ├── styles ├── fonts │ └── ds-digib.ttf └── style.css └── tests └── chronometer.spec.js /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 21 5 | 6 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 7 | exemptLabels: 8 | - bug 9 | - enhancement 10 | 11 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 12 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 13 | daysUntilClose: 7 14 | 15 | # Label to use when marking as stale 16 | staleLabel: stale 17 | 18 | # Comment to post when marking as stale. Set to `false` to disable 19 | markComment: > 20 | This pull request has been automatically marked as stale because it didn't have any recent activity. It will be closed if no further activity occurs. Thank you 21 | for your contributions. 22 | closeComment: > 23 | This pull request is closed. Thank you. 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Automated Testing 2 | on: push 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Load code 8 | uses: actions/checkout@v2 9 | - name: Prepare environment 10 | uses: actions/setup-node@v2 11 | with: 12 | node-version: '14' 13 | check-latest: true 14 | - name: Install dependencies 15 | run: npm i 16 | - name: Run tests 17 | run: npm run test -- --ci --reporters=default --reporters=jest-junit 18 | - name: Reports the results of tests 19 | uses: IgnusG/jest-report-action@v2.3.3 20 | if: always() 21 | with: 22 | access-token: ${{ secrets.GITHUB_TOKEN }} 23 | run-name: test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | package-lock.json 5 | yarn.lock 6 | *.log 7 | lab-solution.html 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | IronChronometer 8 | 9 | 10 | 11 |
12 |

Splits

13 |
    14 |
    15 | 16 |
    17 | 18 |
    19 |
    20 | 0 21 | 0 22 | : 23 | 0 24 | 0 25 |
    26 | 0 27 | 0 28 |
    29 | 30 | 31 |
    32 |
    33 | 34 |
    35 |
    36 |
    37 |
    38 |
    39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /javascript/chronometer.js: -------------------------------------------------------------------------------- 1 | class Chronometer { 2 | constructor() { 3 | // ... your code goes here 4 | } 5 | 6 | start(callback) { 7 | // ... your code goes here 8 | } 9 | 10 | getMinutes() { 11 | // ... your code goes here 12 | } 13 | 14 | getSeconds() { 15 | // ... your code goes here 16 | } 17 | 18 | computeTwoDigitNumber(value) { 19 | // ... your code goes here 20 | } 21 | 22 | stop() { 23 | // ... your code goes here 24 | } 25 | 26 | reset() { 27 | // ... your code goes here 28 | } 29 | 30 | split() { 31 | // ... your code goes here 32 | } 33 | } 34 | 35 | // The following is required to make unit tests work. 36 | /* Environment setup. Do not modify the below code. */ 37 | if (typeof module !== 'undefined') { 38 | module.exports = Chronometer; 39 | } 40 | -------------------------------------------------------------------------------- /javascript/index.js: -------------------------------------------------------------------------------- 1 | const chronometer = new Chronometer(); 2 | 3 | // get the buttons: 4 | const btnLeftElement = document.getElementById('btnLeft'); 5 | const btnRightElement = document.getElementById('btnRight'); 6 | 7 | // get the DOM elements that will serve us to display the time: 8 | const minDecElement = document.getElementById('minDec'); 9 | const minUniElement = document.getElementById('minUni'); 10 | const secDecElement = document.getElementById('secDec'); 11 | const secUniElement = document.getElementById('secUni'); 12 | const milDecElement = document.getElementById('milDec'); 13 | const milUniElement = document.getElementById('milUni'); 14 | const splitsElement = document.getElementById('splits'); 15 | 16 | function printTime() { 17 | // ... your code goes here 18 | } 19 | 20 | function printMinutes() { 21 | // ... your code goes here 22 | } 23 | 24 | function printSeconds() { 25 | // ... your code goes here 26 | } 27 | 28 | // ==> BONUS 29 | function printMilliseconds() { 30 | // ... your code goes here 31 | } 32 | 33 | function printSplit() { 34 | // ... your code goes here 35 | } 36 | 37 | function clearSplits() { 38 | // ... your code goes here 39 | } 40 | 41 | function setStopBtn() { 42 | // ... your code goes here 43 | } 44 | 45 | function setSplitBtn() { 46 | // ... your code goes here 47 | } 48 | 49 | function setStartBtn() { 50 | // ... your code goes here 51 | } 52 | 53 | function setResetBtn() { 54 | // ... your code goes here 55 | } 56 | 57 | // Start/Stop Button 58 | btnLeftElement.addEventListener('click', () => { 59 | // ... your code goes here 60 | }); 61 | 62 | // Reset/Split Button 63 | btnRightElement.addEventListener('click', () => { 64 | // ... your code goes here 65 | }); 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lab-javascript-chronometer", 3 | "version": "1.0.0", 4 | "license": "UNLICENSED", 5 | "scripts": { 6 | "test": "jest", 7 | "test:watch": "jest --watchAll --verbose=false" 8 | }, 9 | "devDependencies": { 10 | "jest": "^26.6.3", 11 | "jest-html-reporter": "^3.3.0", 12 | "jest-junit": "^12.0.0" 13 | }, 14 | "jest": { 15 | "reporters": [ 16 | "default", 17 | [ 18 | "./node_modules/jest-html-reporter", 19 | { 20 | "pageTitle": "Lab Solution", 21 | "outputPath": "lab-solution.html", 22 | "includeFailureMsg": true, 23 | "includeConsoleLog": true 24 | } 25 | ] 26 | ] 27 | }, 28 | "jest-junit": { 29 | "suiteNameTemplate": "{filepath}", 30 | "classNameTemplate": "{classname}", 31 | "titleTemplate": "{title}" 32 | }, 33 | "prettier": { 34 | "singleQuote": true, 35 | "trailingComma": "none" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![logo_ironhack_blue 7](https://user-images.githubusercontent.com/23629340/40541063-a07a0a8a-601a-11e8-91b5-2f13e4e6b441.png) 2 | 3 | # LAB | JS IronChronometer 4 | 5 | ![giphy (1)](https://user-images.githubusercontent.com/76580/167427065-a674fb55-44ea-448a-a7ef-940b45eeb9df.gif) 6 | 7 | ## Introduction 8 | 9 | In this lab, we are going to create a [chronometer](https://www.dictionary.com/browse/chronometer). Chronometers are very commonly used in sports - car racing, athletics, etc. Why wouldn't we practice a bit of our JS and DOM manipulation knowledge and create our own IronChronometer? And then, we can use it to see how many minutes and seconds will take us to complete any of our labs. Sounds like a plan. 10 | 11 | Let's go! 12 | 13 | These are our milestones: 14 | 15 | 1. Our chronometer will have an _LCD screen_, where we will see the minutes and seconds moving forward. 16 | 2. It will also have two different buttons that will change their behavior depending on the status of the chronometer. For example, the start button will become a stop button when the chronometer runs. 17 | 3. As a bonus, we will add a [split functionality](https://www.google.com/search?q=chronometer+split&oq=chronometer+split) allowing us to record the time when we press the split button. 18 | 19 | Let's do it! 20 | 21 | To check how your final version should look like check this **[demo](https://sandrabosk.github.io/demo-chrono/index.html)**. 22 | 23 | ## Requirements 24 | 25 | - Fork this repo. 26 | - Clone this repo. 27 | 28 | 29 | 30 | ## Submission 31 | 32 | - Upon completion, run the following commands: 33 | 34 | ```shell 35 | $ git add . 36 | $ git commit -m "solve iteration x, y, z" 37 | $ git push origin master 38 | ``` 39 | 40 | - Create a Pull Request so that your TAs can check your work. 41 | 42 | ## Instructions 43 | 44 | To kick-off, we are provided with the following files and folders: 45 | 46 | ``` 47 | ├── README.md 48 | ├── index.html 49 | ├── javascript 50 | │ ├── chronometer.js 51 | │ └── index.js 52 | ├── styles 53 | │ ├── fonts 54 | │ │ ├── ds-digi.ttf 55 | │ └── style.css 56 | └── tests 57 | └── chronometer.spec.js 58 | ``` 59 | 60 | The stylesheet already has the `ds-digi` font inserted. This font helps us to have a classic LCD screen to achieve the styles of the traditional chronometers. 61 | 62 | We have also created the clock to let you focus on the JavaScript portion of this exercise. Click below to see the image you should get when you open the `index.html` file: 63 | 64 |
    65 | 66 |
    67 | Click here to see the image 68 | 69 |
    70 | 71 | ![](https://education-team-2020.s3-eu-west-1.amazonaws.com/web-dev/labs/chronometer.png) 72 |
    73 | 74 | 75 | 76 | **This lab is divided into two main parts**: 77 | 78 | - Part 1: the logic (which you will be adding to the `javascript/chronometer.js` file). 79 | - Part 2: the DOM manipulation, so we can visually represent and showcase the previously written logic (the code you will add in the `javascript/index.js`). 80 | 81 | Your solution will require the usage of the global methods `setInterval` and `clearInterval`. 82 | 83 | [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) can be called with a function as first argument and a number of milliseconds as the second argument. It will run said function every number of milliseconds that you passed it. 84 | 85 | When called, `setInterval` returns a number that can be used to identify the _interval_ that was initialized. That same interval can later be stopped by running `clearInterval` and passing it the id of the interval we want to interrupt. 86 | 87 | ### Iteration 1: The logic 88 | 89 | In the first part of the LAB, you will be working on the `javascript/chronometer.js` file. 90 | 91 | 92 | 93 | #### The `Chronometer` class 94 | 95 | Let's create our `Chronometer` class. The `constructor` method shouldn't expect any arguments. It should initialize two properties of the chronometer: 96 | 97 | - `currentTime`, which should start of as the number `0`. 98 | - `intervalId`, which should start as `null`. 99 | 100 | Let's proceed with the creation of the `Chronometer` methods. 101 | 102 | #### Method `start` 103 | 104 | The `Chronometer` class needs to have a `start` method. When called, `start` will start keeping track of time, by running a function in a 1 second interval, which will increment the amount of seconds stored in the property `currentTime` by `1`. 105 | 106 | You should rely on the `setInterval` method to achieve this. The interval id that is returned by calling `setInterval` should be assigned to our `intervalId` property, so this way, we will be able to clear it later on when we need to stop the timer. 107 | 108 | Additionally, the `start` method should accept a function as an argument. Let's name it `callback`. The `callback` argument is optional. If `start` is called and a `callback` is passed, said `callback` should be executed inside of the function you have passed to `setInterval`. If no callback is passed, it should be disregarded (hint: you should check whether _if_ the `callback` was passed before attempting to run it). 109 | 110 | :bulb: _Hint 1_: Keep in mind, if you pass a function declaration to the `setInterval()` method (by writing `setInterval(function () {/* */})`), the keyword `this` will not refer to the object _chronometer_, but to global scope. To enable referencing the chronometer by accessing `this`, pass a function expression (a so-called arrow function) to the `setInterval()` method (by writing `setInterval(() => {/* */})` instead). 111 | 112 | #### Method `getMinutes` 113 | 114 | We're storing the number of seconds that have passed on the `currentTime` property. However, we might want to find out how many minutes have passed. 115 | 116 | The `getMinutes` method should take no arguments, and it should return the _number_ of minutes that have passed as an integer, as a whole number. 117 | We could use the `Math.floor()` method to get a rounded number, using the current time and dividing it by 60. 118 | 119 | #### Method `getSeconds` 120 | 121 | We're now able to get the number of minutes that have passed. But what if we wanted to get the number of seconds that have passed after the start of the current minute? 122 | 123 | The `getSeconds` method should return the number of seconds that have passed after the start of the current minute. 124 | 125 | For example, if the property `currentTime` holds `75`, `getSeconds` should return `15`. If `currentTime` holds `210`, `getSeconds` should return `30`, and so on. 126 | 127 | We could use the module operator (% 60) to get the final number of seconds. 128 | 129 | Hint: The [remainder math operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder) could be tremendously helpful in this situation. 130 | 131 | #### Method `computeTwoDigitNumber` 132 | 133 | Our chronometer has a super cool screen that needs two digits number to display minutes and seconds. However, sometimes the `getMinutes` and `getSeconds` methods return a single-digit number. Let's create a super simple algorithm that will turn into two-digits number any received value. 134 | 135 | The `computeTwoDigitNumber` method should take a number, and return a string where the number received as an argument has been padded with 0s to ensure the value is at least 2 characters long (we could use the `.slice()` method to achieve our goal). 136 | 137 | For example, if `computeTwoDigitNumber` is called with the number `7`, it should return a string with the value of `"07"`. If called with with the number `36`, it should return a string with the value of `"36"`. 138 | 139 | Later, we'll use the `computeTwoDigitNumber` method to format the values returned by `getMinutes` and `getSeconds` and display them in our chronometer. 140 | 141 | #### Method `stop` 142 | 143 | We can already start our chronometer. Let's create a method that stops it. 144 | 145 | When invoked, the `stop` method should clear the interval with the id that had been stored in the `intervalId` property. It's as simple as that. 146 | 147 | :bulb: _Hint_: Use `clearInterval`. 148 | 149 | #### Method `reset` 150 | 151 | The `reset()` will reset our chronometer. Since our code is super clean, we just need to set our `currentTime` property back to 0, and that's it! 152 | 153 | We also need to reset the values in our HTML file, by using `.innerHTML`. 154 | 155 | #### Method `split` 156 | 157 | At certain points, we might want to extract a formatted timestamp for the time elapsed since the chronometer was started. We call this "obtaining the split time". 158 | 159 | The `split` method should expect no arguments, and return a string where the time since the start is formatted as "_mm:ss_". Internally, the `split` method can make usage of previously declared methods such as `getMinutes`, `getSeconds`, and `computeTwoDigitNumber`. 160 | 161 | ### Iteration 2: DOM Manipulation 162 | 163 | Your Chronometer class is now complete! That means that we can go ahead and actually create a visual interface that allows us to use all of the logic we've just coded. 164 | 165 | At this point, you should start working in the `javascript/index.js` file. Note that, for now, you don't have to change anything in the HTML or CSS files. 166 | 167 | In this iteration, your goal is to create a new chronometer, and use its methods (which we previously defined in `chronometer.js`) while interacting with the DOM. Example: when clicked, the `start` button should invoke the chronometer's `start` method. 168 | 169 | As you can see, we have two different buttons: `start` and `clear`. These are the button values when the chronometer is not running. When the chronometer is running, the start button will change its behavior to stop the chronometer. In contrast, the reset button will change to split. 170 | 171 | Both buttons will have different behavior depending on the status of the chronometer. These buttons are `btnLeft` and `btnRight` in our HTML. We can see the different values they will have in the following table: 172 | 173 | | Chronometer Status | Button ID | Text | CSS Class | 174 | | ------------------ | ---------- | ----- | ----------- | 175 | | Stopped | `btnLeft` | START | `btn start` | 176 | | Stopped | `btnRight` | RESET | `btn reset` | 177 | | Running | `btnLeft` | STOP | `btn stop` | 178 | | Running | `btnRight` | SPLIT | `btn split` | 179 | 180 | You will find two click event listeners that are already linked with both `btnLeft` and `btnRight` buttons. You have to create the necessary code to change the status of buttons. 181 | 182 | :bulb: _Hint_: To change the _status_ of the buttons, we have to _toggle_ their classes depending on _if_ their classes include 'start' or 'reset'. 183 | 184 | It means that when we click in the `btnLeft`, if it has the `start` class, you will have to change the `btnLeft` and `btnRight` buttons, setting them up with the Running status described in the table above. 185 | 186 | On the other hand, if the `btnLeft` doesn't have the `start` class when we click, we will have to change both `btnLeft` and `btnRight` properties setting them up with the Stopped status described in the table above. 187 | 188 | If the btnLeft has the class `start`, we should call the `start`method of chronometer - Remember the arguments! -. 189 | 190 | #### Changing buttons texts 191 | 192 | We will be working on the `javascript/index.js` file. We need to do the following (we could use .className and .innerHTML to do so): 193 | 194 | - When the left button is clicked while the chronometer is stopped, we need to: 195 | 196 | - Set the `btnLeft` button with the text STOP, and the class `btn stop` 197 | - Set the `btnRight` button with the text SPLIT, and the class `btn split`. 198 | 199 | - When the left button is clicked while the chronometer is running we need to: 200 | 201 | - Set the `btnLeft` button with the text START, and the class `btn start`. 202 | - Set the `btnRight` button with the text RESET, and the class `btn reset`. 203 | 204 | - In the `index.js` file, create a new instance of the `Chronometer` object (which we already have at the top of the file). 205 | 206 | - Create the necessary code in the `index.js` to call the Chronometer's `start` method if the button has the `start` class, or the `stop` method if the button has the `stop` class applied. 207 | 208 | #### Print our chronometer 209 | 210 | Each second we need to update our screen. So go ahead and create a function that will receive the value for minutes and seconds, and print that on our HTML - remember to reuse our twodigits method that we have in chronometer! 211 | 212 | You can invoke the method start of your chronometer. 213 | 214 | Hint: if you remember, the start method expects a callback as an argument and will execute this callback every second (you can pass as an argument a function to update the user interface) 215 | 216 |
    217 | Click here to see the image 218 | 219 |
    220 | 221 | ![](https://s3-eu-west-1.amazonaws.com/ih-materials/uploads/upload_1a87e0edfb6efea2ae0c77c490e8563b.png) 222 | 223 |
    224 | 225 | ### Iteration 3: Split time 226 | 227 | The following feature we will implement is the split button. Remember that the split button is located in the `btnRight` button when the chronometer is running. In this iteration, we will have to create two different things: HTML & CSS, and the associated JavaScript. 228 | 229 | #### HTML & CSS 230 | 231 | First of all, we have to locate in our `index.html` file an ordered list where we are going to append the current time every time we press the split button - it has an id of `splits`, and we have it targeted at the befinning of our index.js file. 232 | 233 | 234 | #### JavaScript 235 | 236 | Once we have located the ordered list in our HTML, we have to create the button functionality. Every time we click on the split button, we will have to create a new `li` element and append it to the ordered list. The text of this element will be the current time of the chronometer (we have a method on our Chronometer that returns this :wink:). 237 | 238 | Therefore, first we should create a list item every time we click on the button. 239 | After that, we should add a class name to this list item ('list-item'). 240 | Then we should update the innerHTML with the result of our method `split`in chronometer. 241 | And finally we should append it to the parent element in the html file (the ordered list with `id`s of '`splits`) 242 | 243 | 244 |
    245 | Click here to see the image 246 | 247 |
    248 | 249 | ![](https://s3-eu-west-1.amazonaws.com/ih-materials/uploads/upload_a5c9687f25bd710b2e7658ee6d997174.png) 250 | 251 |
    252 | 253 | 254 | ### Iteration 4: Reset 255 | 256 | To finish up with this lesson, we are going to create the _clear_ feature. Remember, we will execute this when the chronometer is stopped, and the user clicks on the right button - check the event listener at the bottom of the file. The behavior here is straightforward: we have to clear all the data on the clock. 257 | 258 | To do that, we will have to set the minutes and seconds to zero in our clock and remove all the `li` elements that we could have in the list we created in the previous iteration. 259 | 260 | ### BONUS Iteration 5: Milliseconds 261 | 262 | Now, we can use our chronometer to calculate how much time we spend on each Ironhack exercise. What happens if we want to calculate our time in a race? We need to be more accurate with our chronometer. How can we be more accurate? By adding milliseconds! 263 | 264 | If we want to add milliseconds to the chronometer, we will have to manipulate the HTML, the CSS, and the JavaScript. In the HTML, we will have to a container to show the milliseconds, changing the style of this container - so we should modify the DOM with the decimal and unit value of the milliseconds (they are targeted at the beginning of the index.js file). 265 | Finally, in JavaScript, we will have to add all the logic to show the milliseconds in the clock. You will also have to add these milliseconds to the split counter - we should call the twoDigits method as well here. 266 | 267 | ![](https://s3-eu-west-1.amazonaws.com/ih-materials/uploads/upload_82e9d1fd5976a3f98bb1382f2385f6a1.png) 268 | 269 | Your goal is to create the JavaScript logic to: 270 | 271 | - Be able to count the milliseconds. 272 | - Show the milliseconds going forward. 273 | - Show the milliseconds when you capture a split time. 274 | - Clear the milliseconds when the Reset button is clicked. 275 | 276 | This lab is a little bit complex, but it will guide you through the logical process of solving the problem and, at the same time, by following the guidelines, you will learn how to separate concerns between the logic and the DOM manipulation (which are the visuals). 277 | 278 | 279 | ## Test Your Code 280 | 281 | 282 | ## Tests, tests, tests! 283 | 284 | This LAB is equipped with unit tests to provide automated feedback on your lab progress. You'll start by working with the tests and using them in conjunction with the iteration instructions. 285 | 286 | Please, open your terminal, change directories into the root of the lab, and run `npm install` to install the test runner. Next run the `npm run test:watch` command to run automated tests. 287 | 288 | ```shell 289 | $ cd lab-javascript-chronometer 290 | $ npm install 291 | $ npm run test:watch 292 | ``` 293 | 294 |
    295 | 296 | Open the resulting `lab-solution.html` file with the [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) VSCode extension to see the test results. 297 | 298 | **Note:** The testing environment and the `lab-solution.html` page don’t allow printing the _console logs_ in the browser. 299 | 300 | To see the console.log outputs you write in any of the JavaScript files, open the `index.html` file using the [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) VSCode extension. 301 | 302 | 303 |
    304 | 305 | **Happy coding!** :heart: 306 | -------------------------------------------------------------------------------- /styles/fonts/ds-digib.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironhack-labs/lab-javascript-chronometer/2c53ec21e707a407fd4b45a4da48e4d08c781c0e/styles/fonts/ds-digib.ttf -------------------------------------------------------------------------------- /styles/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Digi'; 3 | src: url('fonts/ds-digib.ttf'); 4 | } 5 | 6 | body { 7 | font-family: Arial; 8 | font-size: 80px; 9 | font-weight: bold; 10 | padding: 40px 0; 11 | } 12 | 13 | #clock { 14 | border: 2px solid #000; 15 | border-radius: 20px; 16 | box-sizing: border-box; 17 | height: 250px; 18 | margin: 0 auto; 19 | padding: 18px 0; 20 | width: 245px; 21 | } 22 | 23 | #sphere { 24 | border: 2px solid #000; 25 | border-radius: 20px; 26 | box-sizing: border-box; 27 | font-family: 'Digi'; 28 | font-size: 80px; 29 | height: 210px; 30 | margin: 0 auto; 31 | padding: 35px 0; 32 | position: relative; 33 | text-align: center; 34 | width: 220px; 35 | display:flex; 36 | align-items:center; 37 | justify-content:center; 38 | } 39 | 40 | #splits-container { 41 | font-size: 20px; 42 | font-weight: 100; 43 | position: absolute; 44 | left: 50px; 45 | } 46 | 47 | #splits-container li { 48 | padding-bottom: 5px; 49 | text-align: right; 50 | } 51 | 52 | #milliseconds { 53 | font-size: 36px; 54 | position: absolute; 55 | top: 20px; 56 | } 57 | 58 | .leash { 59 | background: rgb(204, 84, 87); 60 | border: 2px solid rgb(134, 59, 61); 61 | height: 50px; 62 | margin: 0 auto; 63 | width: 120px; 64 | } 65 | 66 | .leash-top { 67 | border-bottom: 0; 68 | border-top-left-radius: 10px; 69 | border-top-right-radius: 10px; 70 | } 71 | 72 | .leash-bottom { 73 | border-bottom-left-radius: 10px; 74 | border-bottom-right-radius: 10px; 75 | border-top: 0; 76 | height: 270px; 77 | padding-top: 50px; 78 | } 79 | 80 | .hole { 81 | background: #fff; 82 | border: 2px solid rgb(134, 59, 61); 83 | border-radius: 100%; 84 | height: 20px; 85 | margin: 0 auto 50px; 86 | width: 20px; 87 | } 88 | 89 | .number { 90 | width:40px; 91 | } 92 | 93 | .btn { 94 | border: 0; 95 | border-radius: 100%; 96 | bottom: 10px; 97 | color: #fff; 98 | cursor: pointer; 99 | height: 50px; 100 | outline: 0; 101 | position: absolute; 102 | width: 50px; 103 | } 104 | 105 | .btn.start, 106 | .btn.stop { 107 | left: 10px; 108 | } 109 | 110 | .btn.reset, 111 | .btn.split { 112 | right: 10px; 113 | } 114 | 115 | .btn.start { 116 | background: #5fca5f; 117 | } 118 | .btn.stop { 119 | background: #f14949; 120 | } 121 | .btn.reset { 122 | background: #908e8e; 123 | } 124 | .btn.split { 125 | background: #0851ab; 126 | } 127 | -------------------------------------------------------------------------------- /tests/chronometer.spec.js: -------------------------------------------------------------------------------- 1 | const Chronometer = require('../javascript/chronometer'); 2 | 3 | describe('Chronometer', () => { 4 | let chronometer; 5 | 6 | beforeEach(() => { 7 | jest.useFakeTimers(); 8 | chronometer = new Chronometer(); 9 | }); 10 | 11 | it('should be a class', () => { 12 | expect(typeof Chronometer).toBe('function'); 13 | }); 14 | 15 | it('should not expect any arguments', () => { 16 | expect(Chronometer.length).toEqual(0); 17 | }); 18 | 19 | it('should have a `currentTime` property and its value should start off as 0', () => { 20 | expect(chronometer.currentTime).toBeDefined(); 21 | expect(chronometer.currentTime).toEqual(0); 22 | }); 23 | 24 | it('should have a `intervalId` property and its value should start of as null', () => { 25 | expect(chronometer.intervalId).toBeDefined(); 26 | expect(chronometer.intervalId).toEqual(null); 27 | }); 28 | 29 | describe('"start" method', () => { 30 | it('should be declared', () => { 31 | expect(typeof chronometer.start).toEqual('function'); 32 | }); 33 | 34 | it('should increment by 1 the currentTime property on every 1 second interval', () => { 35 | chronometer.start(); 36 | jest.advanceTimersByTime(1000); 37 | expect(chronometer.currentTime).toEqual(1); 38 | }); 39 | 40 | it('should hold 3 in currentTime property after 3 seconds', () => { 41 | chronometer.start(); 42 | jest.advanceTimersByTime(3000); 43 | expect(chronometer.currentTime).toEqual(3); 44 | }); 45 | }); 46 | 47 | describe('"getMinutes" method', () => { 48 | it('should be declared', () => { 49 | expect(typeof chronometer.getMinutes).toEqual('function'); 50 | }); 51 | 52 | it('should return a number', () => { 53 | chronometer.currentTime = 65; 54 | expect(typeof chronometer.getMinutes()).toEqual('number'); 55 | }); 56 | 57 | it('should return a number without decimal places', () => { 58 | chronometer.currentTime = 65; 59 | expect(chronometer.getMinutes() % 1).toEqual(0); 60 | }); 61 | 62 | it('should return the number of entire minutes passed', () => { 63 | chronometer.currentTime = 65; 64 | expect(chronometer.getMinutes()).toEqual(1); 65 | }); 66 | 67 | it("should return 0 when the chronometer hasn't been started", () => { 68 | expect(chronometer.getMinutes()).toEqual(0); 69 | }); 70 | 71 | it('should return the number of minutes passed even after a very long time', () => { 72 | chronometer.currentTime = 50210; 73 | expect(chronometer.getMinutes()).toEqual(836); 74 | }); 75 | }); 76 | 77 | describe('"getSeconds" method', () => { 78 | it('should be declared', () => { 79 | expect(typeof chronometer.getSeconds).toEqual('function'); 80 | }); 81 | 82 | it('should return a number', () => { 83 | chronometer.currentTime = 3; 84 | expect(typeof chronometer.getSeconds(0)).toEqual('number'); 85 | }); 86 | 87 | it("should return 0 when the currentTime counting haven't started", () => { 88 | chronometer.currentTime = 0; 89 | expect(chronometer.getSeconds()).toEqual(0); 90 | }); 91 | 92 | it('should return the seconds of the currentTime', () => { 93 | chronometer.currentTime = 15; 94 | expect(chronometer.getSeconds()).toEqual(15); 95 | }); 96 | 97 | it('should return the seconds portion of the currentTime that remains after removing the minutes', () => { 98 | chronometer.currentTime = 115; 99 | expect(chronometer.getSeconds()).toEqual(55); 100 | }); 101 | }); 102 | 103 | describe('"computeTwoDigitNumber" method', () => { 104 | it('should be declared', () => { 105 | expect(typeof chronometer.computeTwoDigitNumber).toEqual('function'); 106 | }); 107 | 108 | it('should return a string', () => { 109 | expect(typeof chronometer.computeTwoDigitNumber(7)).toEqual('string'); 110 | }); 111 | 112 | it('should always return a string of length 2', () => { 113 | expect(chronometer.computeTwoDigitNumber(7).length).toEqual(2); 114 | }); 115 | 116 | it("should return '00' when the value is 0", () => { 117 | expect(chronometer.computeTwoDigitNumber(0)).toEqual('00'); 118 | }); 119 | 120 | it("should return '15' when the value is 15", () => { 121 | expect(chronometer.computeTwoDigitNumber(15)).toEqual('15'); 122 | }); 123 | 124 | it("Should return '03' when the value is 3", () => { 125 | expect(chronometer.computeTwoDigitNumber(3)).toEqual('03'); 126 | }); 127 | }); 128 | 129 | describe('"stop" method', () => { 130 | beforeEach(() => { 131 | jest.useFakeTimers(); 132 | }); 133 | 134 | it('should be declared', () => { 135 | expect(typeof chronometer.stop).toEqual('function'); 136 | }); 137 | 138 | it('should call clear the interval', () => { 139 | spyOn(window, 'clearInterval'); 140 | chronometer.stop(); 141 | expect(clearInterval).toHaveBeenCalled(); 142 | }); 143 | 144 | it('should stop a previously started chronometer', () => { 145 | chronometer.start(); 146 | jest.advanceTimersByTime(1000); 147 | expect(chronometer.currentTime).toEqual(1); 148 | chronometer.stop(); 149 | jest.advanceTimersByTime(2000); 150 | expect(chronometer.currentTime).toEqual(1); 151 | }); 152 | }); 153 | 154 | describe('"reset" method', () => { 155 | it('should be declared', () => { 156 | expect(typeof chronometer.reset).toEqual('function'); 157 | }); 158 | 159 | it('should reset the value of the "currentTime" property to 0', () => { 160 | chronometer.currentTime = 5; 161 | chronometer.reset(); 162 | expect(chronometer.currentTime).toEqual(0); 163 | }); 164 | }); 165 | 166 | describe('"split" method', () => { 167 | it('should be declared', () => { 168 | expect(typeof chronometer.split).toEqual('function'); 169 | }); 170 | 171 | it('should return valid format with minutes and seconds', () => { 172 | chronometer.currentTime = 5; 173 | expect(chronometer.split()).toEqual(`00:05`); 174 | chronometer.currentTime = 17; 175 | expect(chronometer.split()).toEqual(`00:17`); 176 | chronometer.currentTime = 60; 177 | expect(chronometer.split()).toEqual(`01:00`); 178 | chronometer.currentTime = 135; 179 | expect(chronometer.split()).toEqual(`02:15`); 180 | chronometer.currentTime = 135; 181 | expect(chronometer.split()).toEqual(`02:15`); 182 | chronometer.currentTime = 800; 183 | expect(chronometer.split()).toEqual(`13:20`); 184 | }); 185 | 186 | // If you decide to work on the bonus iteration, 187 | // comment the previous test and uncomment the following 188 | // it('should return valid format with minutes, seconds and milliseconds', () => { 189 | // let min = chronometer.getMinutes(); 190 | // let sec = chronometer.getSeconds(); 191 | // let milli = chronometer.getMilliseconds(); 192 | // if (min < 10) { 193 | // expect(chronometer.split()).toEqual(`0${min}:0${sec}:0${milli}`); 194 | // } else { 195 | // expect(chronometer.split()).toEqual(`${min}:${sec}:${milli}`); 196 | // } 197 | // }); 198 | }); 199 | }); 200 | --------------------------------------------------------------------------------