├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── extensions └── it-optional.js ├── package.json ├── task ├── 01-strings-tasks.js ├── 02-numbers-tasks.js ├── 03-date-tasks.js ├── 04-arrays-tasks.js ├── 05-regex-tasks.js ├── 06-conditions-n-loops-tasks.js ├── 07-yield-tasks.js ├── 08-objects-tasks.js ├── 09-functions-n-closures-tasks.js ├── 10-katas-1-tasks.js ├── 11-katas-2-tasks.js └── 12-katas-3-tasks.js └── test ├── 01-strings-tests.js ├── 02-numbers-tests.js ├── 03-date-tests.js ├── 04-arrays-tests.js ├── 05-regex-tests.js ├── 06-conditions-n-loops-tests.js ├── 07-yield-tests.js ├── 08-objects-tests.js ├── 09-functions-n-closures-tests.js ├── 10-katas-1-tests.js ├── 11-katas-2-tests.js ├── 12-katas-3-tests.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | node_modules 29 | 30 | # Optional npm cache directory 31 | .npm 32 | 33 | # Optional REPL history 34 | .node_repl_history 35 | 36 | # Editors crap 37 | .vscode 38 | .idea 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5.10.0" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Brest Rolling Scopes](http://brest.rollingscopes.com/images/logo_rs_text.svg)](http://brest.rollingscopes.com/) 2 | #Brest Rolling Scopes School 3 | ## Javascript Assignments [![Build Status](https://travis-ci.org/rolling-scopes-school/js-assignments.svg?branch=master)](https://travis-ci.org/rolling-scopes-school/js-assignments) 4 | 5 | Yet another javascript assignments. There are a lot of interactive javascript resources for beginners, but most of them are online and do not cover the modern programming workflow. There are some excellent training resources on github (https://github.com/rmurphey/js-assessment, https://github.com/mrdavidlaing/javascript-koans, https://github.com/vasanthk/js-bits etc) but they are not exactly simulate the everyday programming process. So the motivation of this project is to show TDD process in the wild to the beginners. Assingment tests are implemented in various ways to feel a difference and gain the experience what manner is good, what is bad and what is ugly. 6 | 7 | Another idea is to prepare assignment to cover all standard javascript functions, to drilling and mastering skills. Some tasks are practical, but some tasks are rather synthetic. 8 | 9 | And the last idea is to inure trainees to work using unit test and feel uncomfortable when programming without tests. 10 | 11 | To start javascript assignments please follow the next steps: 12 | * [Fork this repo](#user-content-how-to-fork-this-repo) 13 | * [Setup travis-ci to test the commits](#user-content-how-to-setup-travis-ci) 14 | * [Setup the work environment](#user-content-how-to-setup-work-environment) 15 | * [Implement assignments using TDD fashion](#user-content-how-to-implement-assignments-using-tdd-fashion) 16 | * [How to debug tasks](#how-to-debug-tasks) 17 | 18 | ### How to fork this repo 19 | * Click the **Fork** button at the top-right corner of this page and the repository will be copied to your own account. 20 | * Run `git clone https://github.com//js-assignments.git` from command line to download the repo. 21 | 22 | ### How to setup travis-ci 23 | * Open [https://travis-ci.org/](https://travis-ci.org/) and sign in with your github account. 24 | * Activate your forked repo **js-assignments**. 25 | * Edit local README.md file and update all links (just replace all occurrences of `'rolling-scopes-school'` with your account name). 26 | * Commit and push updated README.md to github: 27 | ```bash 28 | git add README.md 29 | git commit -m "Update the links" 30 | git push origin master 31 | ``` 32 | * Open https://github.com/rolling-scopes-school/js-assignments and test the build icon. Now it will run all tests and update status once you push changes to github. Keep this icon green! 33 | 34 | 35 | ### How to setup work environment 36 | * Download and install the latest [Nodejs](https://nodejs.org/en/download/stable/). 37 | * Run `npm install` from you repository folder to download the required modules. All dependent modules will be located in the *node_modules* folder. 38 | * Open your favorite editor and complete tasks. 39 | * Open your terminal and use `npm test` command to run all tests. You can run single file by passing it as argument `npm test ./test/01-strings-tests.js`. 40 | * The local repo folder has the following structure:
 41 |     node_modules - app dependences restored by `npm install` command, you can delete this folder and restore later again.
 42 |     task - folder with tasks modules, it's your main folder.
 43 |     test - folder with tests modules to verify the tasks completion.
 44 | 
45 | 46 | ### How to implement assignments using TDD fashion 47 | Now you are ready to implement assignments. Tasks modules are located in the **task** folder. Each module consists of several tasks for specified topic. Each task is usually a regular function: 48 | ```javascript 49 | /** 50 | * Returns the result of concatenation of two strings. 51 | * 52 | * @param {string} value1 53 | * @param {string} value2 54 | * @return {string} 55 | * 56 | * @example 57 | * 'aa', 'bb' => 'aabb' 58 | * 'aa','' => 'aa' 59 | * '', 'bb' => 'bb' 60 | */ 61 | function concatenateStrings(value1, value2) { 62 | throw new Error('Not implemented'); 63 | } 64 | ``` 65 | Resolve this task using the following [TDD steps](https://en.wikipedia.org/wiki/Test-driven_development#Test-driven_development_cycle): 66 | * Run unit tests and make sure that everything is OK and there are no failing tests. 67 | * Read the task description in the comment above the function. Try to understand the idea. If you got it you are to write unit test first, but unit tests are already prepared :) Skip step with writing unit tests. 68 | * Remove the throwing error line from function body 69 | ```javascript 70 | throw new Error('Not implemented'); 71 | ``` 72 | and run the unit tests again. Find one test failed (red). Now it's time to fix it! 73 | * Implement the function by any way and verify your solution by running tests until the failed test become passed (green). 74 | * Your solution work, but now time to refactor it. Try to make your code as pretty and simple as possible keeping up the test green. 75 | * Once you can't improve your code and tests are passed you can commit your solution. 76 | * Push your updates to github server and check if tests passed on [travis-ci](https://travis-ci.org/rolling-scopes-school/js-assignments/builds). 77 | * If everything is OK you can try to resolve the next task. 78 | 79 | ### How to debug tasks 80 | To debug tests you can use **Node inspector**. To install it just run `npm install -g node-inspector` in your terminal. Then follow next steps: 81 | * Add `debugger;` to the first line of your task. 82 | * Run your test file with `npm run test-debug ./test/01-strings-tests.js`. 83 | * In another terminal run `node-inspector` and copy link from the output. 84 | * Open the link in your favorite browser. You should see Chrome Developers Tools like interface where you can debug your tasks. 85 | * When you found and fix your issue, close the browser's tab with the debug tools, stop the node-inspector by pressing Ctrl-C, stop the test runner by pressing Ctrl-C, remove the `debugger;` from your task. 86 | 87 | ### How to debug (beginner's way) 88 | There is an easier way to debug for beginners with free Visual Studio Code: 89 | * Install VSC from https://code.visualstudio.com/ 90 | * Open project folder in VSC and follow the instruction from https://code.visualstudio.com/docs/runtimes/nodejs#_debugging-your-node-application to create a default `launch.json` 91 | * Modify the `launch.json` in the IDE, set the properties "program" and "args" (empty "args" value run all tests, to run particular test specify this test file in "args"): 92 | ``` 93 | { 94 | "version": "0.2.0", 95 | "configurations": [ 96 | { 97 | ... 98 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 99 | ... 100 | "args": ["./test/01-strings-tests.js"], 101 | ... 102 | }, 103 | ... 104 | ] 105 | } 106 | ``` 107 | * Click in the gutter to the left of the line number to set the breakpoint. Press `F5` to run debug. 108 | * NOTE: The `launch.json` is stored in the `.vscode` project folder. 109 | 110 | 111 | ##Contribution 112 | Feel free to contribute into this project. New tasks and katas are welcome. 113 | 114 | -------------------------------------------------------------------------------- /extensions/it-optional.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports = module.exports = testOptional; 4 | 5 | function testOptional(title, fn) { 6 | 7 | it(title, function() { 8 | try { 9 | fn.call(this); 10 | } catch (err) { 11 | if (err.message=="Not implemented") { 12 | this.test.skip(); 13 | } else { 14 | throw err; 15 | } 16 | } 17 | }); 18 | 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-training", 3 | "version": "0.9.1", 4 | "description": "JS training tasks", 5 | "scripts": { 6 | "test": "./node_modules/.bin/mocha", 7 | "test-debug": "./node_modules/.bin/mocha --debug-brk" 8 | }, 9 | "author": "aorgish", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "mocha": "^2.3.4" 13 | }, 14 | "repository" : { 15 | "type" : "git", 16 | "url" : "https://github.com/rolling-scopes-school/js-assignments.git" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /task/01-strings-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String * 7 | * * 8 | ********************************************************************************************/ 9 | 10 | 11 | 12 | /** 13 | * Returns the result of concatenation of two strings. 14 | * 15 | * @param {string} value1 16 | * @param {string} value2 17 | * @return {string} 18 | * 19 | * @example 20 | * 'aa', 'bb' => 'aabb' 21 | * 'aa','' => 'aa' 22 | * '', 'bb' => 'bb' 23 | */ 24 | function concatenateStrings(value1, value2) { 25 | throw new Error('Not implemented'); 26 | } 27 | 28 | 29 | /** 30 | * Returns the length of given string. 31 | * 32 | * @param {string} value 33 | * @return {number} 34 | * 35 | * @example 36 | * 'aaaaa' => 5 37 | * 'b' => 1 38 | * '' => 0 39 | */ 40 | function getStringLength(value) { 41 | throw new Error('Not implemented'); 42 | } 43 | 44 | /** 45 | * Returns the result of string template and given parameters firstName and lastName. 46 | * Please do not use concatenation, use template string : 47 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings 48 | * 49 | * @param {string} firstName 50 | * @param {string} lastName 51 | * @return {string} 52 | * 53 | * @example 54 | * 'John','Doe' => 'Hello, John Doe!' 55 | * 'Chuck','Norris' => 'Hello, Chuck Norris!' 56 | */ 57 | function getStringFromTemplate(firstName, lastName) { 58 | throw new Error('Not implemented'); 59 | } 60 | 61 | /** 62 | * Extracts a name from template string 'Hello, First_Name Last_Name!'. 63 | * 64 | * @param {string} value 65 | * @return {string} 66 | * 67 | * @example 68 | * 'Hello, John Doe!' => 'John Doe' 69 | * 'Hello, Chuck Norris!' => 'Chuck Norris' 70 | */ 71 | function extractNameFromTemplate(value) { 72 | throw new Error('Not implemented'); 73 | } 74 | 75 | 76 | /** 77 | * Returns a first char of the given string. 78 | * 79 | * @param {string} value 80 | * @return {string} 81 | * 82 | * @example 83 | * 'John Doe' => 'J' 84 | * 'cat' => 'c' 85 | */ 86 | function getFirstChar(value) { 87 | throw new Error('Not implemented'); 88 | } 89 | 90 | /** 91 | * Removes a leading and trailing whitespace characters from string. 92 | * 93 | * @param {string} value 94 | * @return {string} 95 | * 96 | * @example 97 | * ' Abracadabra' => 'Abracadabra' 98 | * 'cat' => 'cat' 99 | * '\tHello, World! ' => 'Hello, World!' 100 | */ 101 | function removeLeadingAndTrailingWhitespaces(value) { 102 | throw new Error('Not implemented'); 103 | } 104 | 105 | /** 106 | * Returns a string that repeated the specified number of times. 107 | * 108 | * @param {string} value 109 | * @param {string} count 110 | * @return {string} 111 | * 112 | * @example 113 | * 'A', 5 => 'AAAAA' 114 | * 'cat', 3 => 'catcatcat' 115 | */ 116 | function repeatString(value, count) { 117 | throw new Error('Not implemented'); 118 | } 119 | 120 | /** 121 | * Remove the first occurrence of string inside another string 122 | * 123 | * @param {string} str 124 | * @param {string} value 125 | * @return {string} 126 | * 127 | * @example 128 | * 'To be or not to be', 'not' => 'To be or to be' 129 | * 'I like legends', 'end' => 'I like legs', 130 | * 'ABABAB','BA' => 'ABAB' 131 | */ 132 | function removeFirstOccurrences(str, value) { 133 | throw new Error('Not implemented'); 134 | } 135 | 136 | /** 137 | * Remove the first and last angle brackets from tag string 138 | * 139 | * @param {string} str 140 | * @return {string} 141 | * 142 | * @example 143 | * '
' => 'div' 144 | * '' => 'span' 145 | * '' => 'a' 146 | */ 147 | function unbracketTag(str) { 148 | throw new Error('Not implemented'); 149 | } 150 | 151 | 152 | /** 153 | * Converts all characters of the specified string into the upper case 154 | * 155 | * @param {string} str 156 | * @return {string} 157 | * 158 | * @example 159 | * 'Thunderstruck' => 'THUNDERSTRUCK' 160 | * 'abcdefghijklmnopqrstuvwxyz' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 161 | */ 162 | function convertToUpperCase(str) { 163 | throw new Error('Not implemented'); 164 | } 165 | 166 | /** 167 | * Extracts e-mails from single string with e-mails list delimeted by semicolons 168 | * 169 | * @param {string} str 170 | * @return {array} 171 | * 172 | * @example 173 | * 'angus.young@gmail.com;brian.johnson@hotmail.com;bon.scott@yahoo.com' => ['angus.young@gmail.com', 'brian.johnson@hotmail.com', 'bon.scott@yahoo.com'] 174 | * 'info@gmail.com' => ['info@gmail.com'] 175 | */ 176 | function extractEmails(str) { 177 | throw new Error('Not implemented'); 178 | } 179 | 180 | /** 181 | * Returns the string representation of rectangle with specified width and height 182 | * using pseudograhic chars 183 | * 184 | * @param {number} width 185 | * @param {number} height 186 | * @return {string} 187 | * 188 | * @example 189 | * 190 | * '┌────┐\n'+ 191 | * (6,4) => '│ │\n'+ 192 | * '│ │\n'+ 193 | * '└────┘\n' 194 | * 195 | * (2,2) => '┌┐\n'+ 196 | * '└┘\n' 197 | * 198 | * '┌──────────┐\n'+ 199 | * (12,3) => '│ │\n'+ 200 | * '└──────────┘\n' 201 | * 202 | */ 203 | function getRectangleString(width, height) { 204 | throw new Error('Not implemented'); 205 | } 206 | 207 | 208 | /** 209 | * Encode specified string with ROT13 cipher 210 | * See details: https://en.wikipedia.org/wiki/ROT13 211 | * 212 | * @param {string} str 213 | * @return {string} 214 | * 215 | * @example 216 | * 217 | * 'hello' => 'uryyb' 218 | * 'Why did the chicken cross the road?' => 'Jul qvq gur puvpxra pebff gur ebnq?' 219 | * 'Gb trg gb gur bgure fvqr!' => 'To get to the other side!' 220 | * 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' => 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm' 221 | * 222 | */ 223 | function encodeToRot13(str) { 224 | throw new Error('Not implemented'); 225 | } 226 | 227 | /** 228 | * Returns true if the value is string; otherwise false. 229 | * @param {string} value 230 | * @return {boolean} 231 | * 232 | * @example 233 | * isString() => false 234 | * isString(null) => false 235 | * isString([]) => false 236 | * isString({}) => false 237 | * isString('test') => true 238 | * isString(new String('test')) => true 239 | */ 240 | function isString(value) { 241 | throw new Error('Not implemented'); 242 | } 243 | 244 | 245 | /** 246 | * Returns playid card id. 247 | * 248 | * Playing cards inittial deck inclides the cards in the following order: 249 | * 250 | * 'A♣','2♣','3♣','4♣','5♣','6♣','7♣','8♣','9♣','10♣','J♣','Q♣','K♣', 251 | * 'A♦','2♦','3♦','4♦','5♦','6♦','7♦','8♦','9♦','10♦','J♦','Q♦','K♦', 252 | * 'A♥','2♥','3♥','4♥','5♥','6♥','7♥','8♥','9♥','10♥','J♥','Q♥','K♥', 253 | * 'A♠','2♠','3♠','4♠','5♠','6♠','7♠','8♠','9♠','10♠','J♠','Q♠','K♠' 254 | * 255 | * (see https://en.wikipedia.org/wiki/Standard_52-card_deck) 256 | * Function returns the zero-based index of specified card in the initial deck above. 257 | * 258 | * @param {string} value 259 | * @return {number} 260 | * 261 | * @example 262 | * 'A♣' => 0 263 | * '2♣' => 1 264 | * '3♣' => 2 265 | * ... 266 | * 'Q♠' => 50 267 | * 'K♠' => 51 268 | */ 269 | function getCardId(value) { 270 | throw new Error('Not implemented'); 271 | } 272 | 273 | 274 | module.exports = { 275 | concatenateStrings: concatenateStrings, 276 | getStringLength: getStringLength, 277 | getStringFromTemplate: getStringFromTemplate, 278 | extractNameFromTemplate: extractNameFromTemplate, 279 | getFirstChar: getFirstChar, 280 | removeLeadingAndTrailingWhitespaces: removeLeadingAndTrailingWhitespaces, 281 | repeatString: repeatString, 282 | removeFirstOccurrences: removeFirstOccurrences, 283 | unbracketTag: unbracketTag, 284 | convertToUpperCase: convertToUpperCase, 285 | extractEmails: extractEmails, 286 | getRectangleString: getRectangleString, 287 | encodeToRot13: encodeToRot13, 288 | isString: isString, 289 | getCardId: getCardId 290 | }; 291 | -------------------------------------------------------------------------------- /task/02-numbers-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_dates * 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number * 8 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math * 9 | * * 10 | ********************************************************************************************/ 11 | 12 | 13 | /** 14 | * Returns an area of a rectangle given by width and heigth. 15 | * 16 | * @param {numder} width 17 | * @param {number} height 18 | * @return {number} 19 | * 20 | * @example: 21 | * 5, 10 => 50 22 | * 5, 5 => 25 23 | */ 24 | function getRectangleArea(width, height) { 25 | throw new Error('Not implemented'); 26 | } 27 | 28 | 29 | /** 30 | * Returns a circumference of circle given by radius. 31 | * 32 | * @param {number} radius 33 | * @return {number} 34 | * 35 | * @example: 36 | * 5 => 31.41592653589793 37 | * 3.14 => 19.729201864543903 38 | * 0 => 0 39 | */ 40 | function getCicleCircumference(radius) { 41 | throw new Error('Not implemented'); 42 | } 43 | 44 | /** 45 | * Returns an average of two given numbers. 46 | * 47 | * @param {numder} value1 48 | * @param {number} value2 49 | * @return {number} 50 | * 51 | * @example: 52 | * 5, 5 => 5 53 | * 10, 0 => 5 54 | * -3, 3 => 0 55 | */ 56 | function getAverage(value1, value2) { 57 | throw new Error('Not implemented'); 58 | } 59 | 60 | /** 61 | * Returns a distance beetween two points by cartesian coordinates. 62 | * 63 | * @param {number} x1 64 | * @param {number} y1 65 | * @param {number} x2 66 | * @param {number} y2 67 | * 68 | * @return {number} 69 | * 70 | * @example: 71 | * (0,0) (0,1) => 1 72 | * (0,0) (1,0) => 1 73 | * (-5,0) (10,-10) => 18.027756377319946 74 | */ 75 | function getDistanceBetweenPoints(x1, y1, x2, y2) { 76 | throw new Error('Not implemented'); 77 | } 78 | 79 | /** 80 | * Returns a root of linear equation a*x + b = 0 given by coefficients a and b. 81 | * 82 | * @param {number} a 83 | * @param {number} b 84 | * @return {number} 85 | * 86 | * @example: 87 | * 5*x - 10 = 0 => 2 88 | * x + 8 = 0 => -8 89 | * 5*x = 0 => 0 90 | */ 91 | function getLinearEquationRoot(a, b) { 92 | throw new Error('Not implemented'); 93 | } 94 | 95 | 96 | /** 97 | * Returns an angle (in radians) between two vectors given by xi and yi, coordinates in Cartesian plane 98 | * See details https://en.wikipedia.org/wiki/Euclidean_vector#Representations 99 | * 100 | * @param {number} x1 101 | * @param {number} y1 102 | * @param {number} x2 103 | * @param {number} y2 104 | * @return {number} 105 | * 106 | * @example: 107 | * (1,0) (0,1) => π/2 108 | * (0,1) (0,-1) => π 109 | * (0,-1) (1,0) => π/2 110 | * (0,1) (0,1) => 0 111 | * (0,1) (1,2) => 0 112 | */ 113 | function getAngleBetweenVectors(x1, y1, x2, y2) { 114 | throw new Error('Not implemented'); 115 | } 116 | 117 | /** 118 | * Returns a last digit of a integer number. 119 | * 120 | * @param {number} value 121 | * @return {number} 122 | * 123 | * @example: 124 | * 100 => 0 125 | * 37 => 7 126 | * 5 => 5 127 | * 0 => 0 128 | */ 129 | function getLastDigit(value) { 130 | throw new Error('Not implemented'); 131 | } 132 | 133 | 134 | /** 135 | * Returns a number by given string representation. 136 | * 137 | * @param {string} value 138 | * @return {number} 139 | * 140 | * @example: 141 | * '100' => 100 142 | * '37' => 37 143 | * '-525.5' => -525.5 144 | */ 145 | function parseNumberFromString(value) { 146 | throw new Error('Not implemented'); 147 | } 148 | 149 | /** 150 | * Returns a diagonal length of the rectangular parallelepiped given by its sides a,b,c. 151 | * 152 | * @param {number} a 153 | * @param {number} b 154 | * @param {number} c 155 | * @return {number} 156 | * 157 | * @example: 158 | * 1,1,1 => 1.7320508075688772 159 | * 3,3,3 => 5.196152422706632 160 | * 1,2,3 => 3.741657386773941 161 | */ 162 | function getParallelipidedDiagonal(a,b,c) { 163 | throw new Error('Not implemented'); 164 | } 165 | 166 | /** 167 | * Returns the number rounded to specified power of 10. 168 | * 169 | * @param {number} num 170 | * @param {number} pow 171 | * @return {number} 172 | * 173 | * @example: 174 | * 1234, 0 => 1234 175 | * 1234, 1 => 1230 176 | * 1234, 2 => 1200 177 | * 1234, 3 => 1000 178 | * 1678, 0 => 1678 179 | * 1678, 1 => 1680 180 | * 1678, 2 => 1700 181 | * 1678, 3 => 2000 182 | */ 183 | function roundToPowerOfTen(num, pow) { 184 | throw new Error('Not implemented'); 185 | } 186 | 187 | /** 188 | * Returns true is the number is prime; otherwise false. 189 | * See: https://en.wikipedia.org/wiki/Primality_test 190 | * 191 | * @param {number} n 192 | * @return {bool} 193 | * 194 | * @example: 195 | * 4 => false 196 | * 5 => true 197 | * 6 => false 198 | * 7 => true 199 | * 11 => true 200 | * 12 => false 201 | * 16 => false 202 | * 17 => true 203 | */ 204 | function isPrime(n) { 205 | throw new Error('Not implemented'); 206 | } 207 | 208 | /** 209 | * Tries to convert value to number and returns it if conversion was successfull; 210 | * otherwise returns default value passed as a second argument. 211 | * 212 | * @param {any} value 213 | * @param {any} def 214 | * @return {number} 215 | * 216 | * @example 217 | * toNumber(null, 0) => 0 218 | * toNumber('test', 0) => 0 219 | * toNumber('1', 0) => 1 220 | * toNumber(42, 0) => 42 221 | * toNumber(new Number(42), 0) => 42 222 | */ 223 | function toNumber(value, def) { 224 | throw new Error('Not implemented'); 225 | } 226 | 227 | module.exports = { 228 | getRectangleArea: getRectangleArea, 229 | getCicleCircumference: getCicleCircumference, 230 | getAverage: getAverage, 231 | getDistanceBetweenPoints: getDistanceBetweenPoints, 232 | getLinearEquationRoot: getLinearEquationRoot, 233 | getAngleBetweenVectors: getAngleBetweenVectors, 234 | getLastDigit: getLastDigit, 235 | parseNumberFromString: parseNumberFromString, 236 | getParallelipidedDiagonal: getParallelipidedDiagonal, 237 | roundToPowerOfTen: roundToPowerOfTen, 238 | isPrime: isPrime, 239 | toNumber: toNumber 240 | }; 241 | -------------------------------------------------------------------------------- /task/03-date-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Numbers_and_dates#Date_object 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date * 8 | * * 9 | ********************************************************************************************/ 10 | 11 | 12 | /** 13 | * Parses a rfc2822 string date representation into date value 14 | * For rfc2822 date specification refer to : http://tools.ietf.org/html/rfc2822#page-14 15 | * 16 | * @param {string} value 17 | * @return {date} 18 | * 19 | * @example: 20 | * 'December 17, 1995 03:24:00' => Date() 21 | * 'Tue, 26 Jan 2016 13:48:02 GMT' => Date() 22 | * 'Sun, 17 May 1998 03:00:00 GMT+01' => Date() 23 | */ 24 | function parseDataFromRfc2822(value) { 25 | throw new Error('Not implemented'); 26 | } 27 | 28 | /** 29 | * Parses an ISO 8601 string date representation into date value 30 | * For ISO 8601 date specification refer to : https://en.wikipedia.org/wiki/ISO_8601 31 | * 32 | * @param {string} value 33 | * @return {date} 34 | * 35 | * @example : 36 | * '2016-01-19T16:07:37+00:00' => Date() 37 | * '2016-01-19T08:07:37Z' => Date() 38 | */ 39 | function parseDataFromIso8601(value) { 40 | throw new Error('Not implemented'); 41 | } 42 | 43 | 44 | /** 45 | * Returns true if specified date is leap year and false otherwise 46 | * Please find algorithm here: https://en.wikipedia.org/wiki/Leap_year#Algorithm 47 | * 48 | * @param {date} date 49 | * @return {bool} 50 | * 51 | * @example : 52 | * Date(1900,1,1) => false 53 | * Date(2000,1,1) => true 54 | * Date(2001,1,1) => false 55 | * Date(2012,1,1) => true 56 | * Date(2015,1,1) => false 57 | */ 58 | function isLeapYear(date) { 59 | throw new Error('Not implemented'); 60 | } 61 | 62 | 63 | /** 64 | * Returns the string represention of the timespan between two dates. 65 | * The format of output string is "HH:mm:ss.sss" 66 | * 67 | * @param {date} startDate 68 | * @param {date} endDate 69 | * @return {string} 70 | * 71 | * @example: 72 | * Date(2000,1,1,10,0,0), Date(2000,1,1,11,0,0) => "01:00:00.000" 73 | * Date(2000,1,1,10,0,0), Date(2000,1,1,10,30,0) => "00:30:00.000" 74 | * Date(2000,1,1,10,0,0), Date(2000,1,1,10,0,20) => "00:00:20.000" 75 | * Date(2000,1,1,10,0,0), Date(2000,1,1,10,0,0,250) => "00:00:00.250" 76 | * Date(2000,1,1,10,0,0), Date(2000,1,1,15,20,10,453) => "05:20:10.453" 77 | */ 78 | function timeSpanToString(startDate, endDate) { 79 | throw new Error('Not implemented'); 80 | } 81 | 82 | 83 | /** 84 | * Returns the angle (in radians) between the hands of an analog clock for the specified Greenwich time. 85 | * If you have problem with solution please read: https://en.wikipedia.org/wiki/Clock_angle_problem 86 | * 87 | * @param {date} date 88 | * @return {number} 89 | * 90 | * @example: 91 | * Date.UTC(2016,2,5, 0, 0) => 0 92 | * Date.UTC(2016,3,5, 3, 0) => Math.PI/2 93 | * Date.UTC(2016,3,5,18, 0) => Math.PI 94 | * Date.UTC(2016,3,5,21, 0) => Math.PI/2 95 | */ 96 | function angleBetweenClockHands(date) { 97 | throw new Error('Not implemented'); 98 | } 99 | 100 | 101 | module.exports = { 102 | parseDataFromRfc2822: parseDataFromRfc2822, 103 | parseDataFromIso8601: parseDataFromIso8601, 104 | isLeapYear: isLeapYear, 105 | timeSpanToString: timeSpanToString, 106 | angleBetweenClockHands: angleBetweenClockHands 107 | }; 108 | -------------------------------------------------------------------------------- /task/04-arrays-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /********************************************************************************************* 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array * 7 | * * 8 | * NOTE : Please do not use loops! All tasks can be implmeneted using standard Array methods * 9 | * * 10 | *********************************************************************************************/ 11 | 12 | 13 | /** 14 | * Returns an index of the specified element in array or -1 if element is not found 15 | * 16 | * @param {array} arr 17 | * @param {any} value 18 | * @return {number} 19 | * 20 | * @example 21 | * ['Ace', 10, true], 10 => 1 22 | * ['Array', 'Number', 'string'], 'Date' => -1 23 | * [0, 1, 2, 3, 4, 5], 5 => 5 24 | */ 25 | function findElement(arr, value) { 26 | throw new Error('Not implemented'); 27 | } 28 | 29 | /** 30 | * Generates an array of odd numbers of the specified length 31 | * 32 | * @param {number} len 33 | * @return {array} 34 | * 35 | * @example 36 | * 1 => [ 1 ] 37 | * 2 => [ 1, 3 ] 38 | * 5 => [ 1, 3, 5, 7, 9 ] 39 | */ 40 | function generateOdds(len) { 41 | throw new Error('Not implemented'); 42 | } 43 | 44 | 45 | /** 46 | * Returns the doubled array - elements of the specified array are repeated twice using original order 47 | * 48 | * @param {array} arr 49 | * @return {array} 50 | * 51 | * @example 52 | * ['Ace', 10, true] => ['Ace', 10, true, 'Ace', 10, true] 53 | * [0, 1, 2, 3, 4, 5] => [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5] 54 | * [] => [] 55 | */ 56 | function doubleArray(arr) { 57 | throw new Error('Not implemented'); 58 | } 59 | 60 | 61 | /** 62 | * Returns an array of positive numbers from the specified array in original order 63 | * 64 | * @param {array} arr 65 | * @return {array} 66 | * 67 | * @example 68 | * [ 0, 1, 2, 3, 4, 5 ] => [ 1, 2, 3, 4, 5 ] 69 | * [-1, 2, -5, -4, 0] => [ 2 ] 70 | * [] => [] 71 | */ 72 | function getArrayOfPositives(arr) { 73 | throw new Error('Not implemented'); 74 | } 75 | 76 | /** 77 | * Returns the array with strings only in the specified array (in original order) 78 | * 79 | * @param {array} arr 80 | * @return {array} 81 | * 82 | * @example 83 | * [ 0, 1, 'cat', 3, true, 'dog' ] => [ 'cat', 'dog' ] 84 | * [ 1, 2, 3, 4, 5 ] => [] 85 | * [ 'cat, 'dog', 'raccon' ] => [ 'cat', 'dog', 'racoon' ] 86 | */ 87 | function getArrayOfStrings(arr) { 88 | throw new Error('Not implemented'); 89 | } 90 | 91 | /** 92 | * Removes falsy values from the specified array 93 | * Falsy values: false, null, 0, "", undefined, and NaN. 94 | * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description) 95 | * 96 | * @param {array} arr 97 | * @return {array} 98 | * 99 | * @example 100 | * [ 0, false, 'cat', NaN, true, '' ] => [ 'cat', true ] 101 | * [ 1, 2, 3, 4, 5, 'false' ] => [ 1, 2, 3, 4, 5, 'false' ] 102 | * [ false, 0, NaN, '', undefined ] => [ ] 103 | */ 104 | function removeFalsyValues(arr) { 105 | throw new Error('Not implemented'); 106 | } 107 | 108 | /** 109 | * Returns the array of useprcase strings from the specified array 110 | * 111 | * @param {array} arr 112 | * @return {array} 113 | * 114 | * @example 115 | * [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ] => [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ] 116 | * [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ] => [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ] 117 | */ 118 | function getUpperCaseStrings(arr) { 119 | throw new Error('Not implemented'); 120 | } 121 | 122 | 123 | /** 124 | * Returns the array of string lengths from the specified string array. 125 | * 126 | * @param {array} arr 127 | * @return {array} 128 | * 129 | * @example 130 | * [ '', 'a', 'bc', 'def', 'ghij' ] => [ 0, 1, 2, 3, 4 ] 131 | * [ 'angular', 'react', 'ember' ] => [ 7, 5, 5 ] 132 | */ 133 | function getStringsLength(arr) { 134 | throw new Error('Not implemented'); 135 | } 136 | 137 | /** 138 | * Inserts the item into specified array at specified index 139 | * 140 | * @param {array} arr 141 | * @param {any} item 142 | * @param {number} index 143 | * 144 | * @example 145 | * [ 1, 3, 4, 5 ], 2, 1 => [ 1, 2, 3, 4, 5 ] 146 | * [ 1, 'b', 'c'], 0, 'x' => [ 'x', 1, 'b', 'c' ] 147 | */ 148 | function insertItem(arr, item, index) { 149 | throw new Error('Not implemented'); 150 | } 151 | 152 | /** 153 | * Returns the n first items of the specified array 154 | * 155 | * @param {array} arr 156 | * @param {number} n 157 | * 158 | * @example 159 | * [ 1, 3, 4, 5 ], 2 => [ 1, 2 ] 160 | * [ 'a', 'b', 'c', 'd'], 3 => [ 'a', 'b', 'c' ] 161 | */ 162 | function getHead(arr, n) { 163 | throw new Error('Not implemented'); 164 | } 165 | 166 | 167 | /** 168 | * Returns the n last items of the specified array 169 | * 170 | * @param {array} arr 171 | * @param {number} n 172 | * 173 | * @example 174 | * [ 1, 3, 4, 5 ], 2 => [ 4, 5 ] 175 | * [ 'a', 'b', 'c', 'd'], 3 => [ 'b', 'c', 'd' ] 176 | */ 177 | function getTail(arr, n) { 178 | throw new Error('Not implemented'); 179 | } 180 | 181 | 182 | /** 183 | * Returns CSV represebtation of two-dimentional numeric array. 184 | * https://en.wikipedia.org/wiki/Comma-separated_values 185 | * 186 | * @param {array} arr 187 | * @return {string} 188 | * 189 | * @example 190 | * [ 191 | * [ 0, 1, 2, 3, 4 ], 192 | * [ 10,11,12,13,14 ], 193 | * [ 20,21,22,23,24 ], 194 | * [ 30,31,32,33,34 ] 195 | * ] 196 | * => 197 | * '0,1,2,3,4\n' 198 | * +'10,11,12,13,14\n' 199 | * +'20,21,22,23,24\n' 200 | * +'30,31,32,33,34' 201 | */ 202 | function toCsvText(arr) { 203 | throw new Error('Not implemented'); 204 | } 205 | 206 | /** 207 | * Transforms the numeric array into the according array of squares: 208 | * f(x) = x * x 209 | * 210 | * @param {array} arr 211 | * @return {array} 212 | * 213 | * @example 214 | * [ 0, 1, 2, 3, 4, 5 ] => [ 0, 1, 4, 9, 16, 25 ] 215 | * [ 10, 100, -1 ] => [ 100, 10000, 1 ] 216 | */ 217 | function toArrayOfSquares(arr) { 218 | throw new Error('Not implemented'); 219 | } 220 | 221 | 222 | /** 223 | * Transforms the numeric array to the according moving sum array: 224 | * f[n] = x[0] + x[1] + x[2] +...+ x[n] 225 | * or f[n] = f[n-1] + x[n] 226 | * 227 | * @param {array} arr 228 | * @return {array} 229 | * 230 | * Example : 231 | * [ 1, 1, 1, 1, 1 ] => [ 1, 2, 3, 4, 5 ] 232 | * [ 10, -10, 10, -10, 10 ] => [ 10, 0, 10, 0, 10 ] 233 | * [ 0, 0, 0, 0, 0] => [ 0, 0, 0, 0, 0] 234 | * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ] 235 | */ 236 | function getMovingSum(arr) { 237 | throw new Error('Not implemented'); 238 | } 239 | 240 | /** 241 | * Returns every second item from the specified array: 242 | * 243 | * @param {array} arr 244 | * @return {array} 245 | * 246 | * Example : 247 | * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 2, 4, 6, 8, 10 ] 248 | * [ 'a', 'b', 'c' , null ] => [ "b", null ] 249 | * [ "a" ] => [] 250 | */ 251 | function getSecondItems(arr) { 252 | throw new Error('Not implemented'); 253 | } 254 | 255 | 256 | /** 257 | * Propagates every item in sequence its position times 258 | * Returns an array that consists of: one first item, two second items, tree third items etc. 259 | * 260 | * @param {array} arr 261 | * @return {array} 262 | * 263 | * @example : 264 | * [] => [] 265 | * [ 1 ] => [ 1 ] 266 | * [ 'a', 'b' ] => [ 'a', 'b','b' ] 267 | * [ 'a', 'b', 'c', null ] => [ 'a', 'b','b', 'c','c','c', null,null,null,null ] 268 | * [ 1,2,3,4,5 ] => [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ] 269 | */ 270 | function propagateItemsByPositionIndex(arr) { 271 | throw new Error('Not implemented'); 272 | } 273 | 274 | 275 | /** 276 | * Returns the 3 largest numbers from the specified array 277 | * 278 | * @param {array} arr 279 | * @return {array} 280 | * 281 | * @example 282 | * [] => [] 283 | * [ 1, 2 ] => [ 2, 1 ] 284 | * [ 1, 2, 3 ] => [ 3, 2, 1 ] 285 | * [ 1,2,3,4,5,6,7,8,9,10 ] => [ 10, 9, 8 ] 286 | * [ 10, 10, 10, 10 ] => [ 10, 10, 10 ] 287 | */ 288 | function get3TopItems(arr) { 289 | throw new Error('Not implemented'); 290 | } 291 | 292 | 293 | /** 294 | * Returns the number of positive numbers from specified array 295 | * 296 | * @param {array} arr 297 | * @return {number} 298 | * 299 | * @example 300 | * [ ] => 0 301 | * [ -1, 0, 1 ] => 1 302 | * [ 1, 2, 3] => 3 303 | * [ null, 1, 'elephant' ] => 1 304 | * [ 1, '2' ] => 1 305 | */ 306 | function getPositivesCount(arr) { 307 | throw new Error('Not implemented'); 308 | } 309 | 310 | /** 311 | * Sorts digit names 312 | * 313 | * @param {array} arr 314 | * @return {array} 315 | * 316 | * @example 317 | * [] => [] 318 | * [ 'nine','one' ] => [ 'one', 'nine' ] 319 | * [ 'one','two','three' ] => [ 'one','two', 'three' ] 320 | * [ 'nine','eight','nine','eight'] => [ 'eight','eight','nine','nine'] 321 | * [ 'one','one','one','zero' ] => [ 'zero','one','one','one' ] 322 | */ 323 | function sortDigitNamesByNumericOrder(arr) { 324 | throw new Error('Not implemented'); 325 | } 326 | 327 | /** 328 | * Returns the sum of all items in the specified array of numbers 329 | * 330 | * @param {array} arr 331 | * @return {number} 332 | * 333 | * @example 334 | * [] => 0 335 | * [ 1, 2, 3 ] => 6 336 | * [ -1, 1, -1, 1 ] => 0 337 | * [ 1, 10, 100, 1000 ] => 1111 338 | */ 339 | function getItemsSum(arr) { 340 | throw new Error('Not implemented'); 341 | } 342 | 343 | /** 344 | * Returns the number of all falsy value in the specified array 345 | * 346 | * @param {array} arr 347 | * @return {array} 348 | * 349 | * @example 350 | * [] => 0 351 | * [ 1, '', 3 ] => 1 352 | * [ -1, 'false', null, 0 ] => 2 353 | * [ null, undefined, NaN, false, 0, '' ] => 6 354 | */ 355 | function getFalsyValuesCount(arr) { 356 | throw new Error('Not implemented'); 357 | } 358 | 359 | /** 360 | * Returns a number of all occurences of the specified item in an array 361 | * 362 | * @param {array} arr 363 | * @param {any} item 364 | * @return {number} 365 | * 366 | * @example 367 | * [ 0, 0, 1, 1, 1, 2 ], 1 => 3 368 | * [ 1, 2, 3, 4, 5 ], 0 => 0 369 | * [ 'a','b','c','c' ], 'c'=> 2 370 | * [ null, undefined, null ], null => 2 371 | * [ true, 0, 1, 'true' ], true => 1 372 | */ 373 | function findAllOccurences(arr, item) { 374 | throw new Error('Not implemented'); 375 | } 376 | 377 | /** 378 | * Concatenates all elements from specified array into single string with ',' delimeter 379 | * 380 | * @param {array} arr 381 | * @return {string} 382 | * 383 | * @example 384 | * [0, false, 'cat', NaN, true, ''] => '0,false,cat,NaN,true,' 385 | * [1, 2, 3, 4, 5] => '1,2,3,4,5' 386 | * ['rock', 'paper', 'scissors'] => 'rock,paper,scissors' 387 | */ 388 | function toStringList(arr) { 389 | throw new Error('Not implemented'); 390 | } 391 | 392 | 393 | /** 394 | * Sorts the specified array by country name first and city name (if countries are equal) in ascending order. 395 | * 396 | * @param {array} arr 397 | * @return {array} 398 | * 399 | * @example 400 | * [ 401 | * { country: 'Russia', city: 'Moscow' }, 402 | * { country: 'Belarus', city: 'Minsk' }, 403 | * { country: 'Poland', city: 'Warsaw' }, 404 | * { country: 'Russia', city: 'Saint Petersburg' }, 405 | * { country: 'Poland', city: 'Krakow' }, 406 | * { country: 'Belarus', city: 'Brest' } 407 | * ] 408 | * => 409 | * [ 410 | * { country: 'Belarus', city: 'Brest' }, 411 | * { country: 'Belarus', city: 'Minsk' }, 412 | * { country: 'Poland', city: 'Krakow' }, 413 | * { country: 'Poland', city: 'Warsaw' }, 414 | * { country: 'Russia', city: 'Moscow' }, 415 | * { country: 'Russia', city: 'Saint Petersburg' } 416 | */ 417 | function sortCitiesArray(arr) { 418 | throw new Error('Not implemented'); 419 | } 420 | 421 | /** 422 | * Creates an indentity matrix of the specified size 423 | * 424 | * @param {number} n 425 | * @return {array} 426 | * 427 | * @example 428 | * 1 => [[1]] 429 | * 430 | * 2 => [[1,0], 431 | * [0,1]] 432 | * 433 | * [[1,0,0,0,0], 434 | * [0,1,0,0,0], 435 | * 5 => [0,0,1,0,0], 436 | * [0,0,0,1,0], 437 | * [0,0,0,0,1]] 438 | */ 439 | function getIdentityMatrix(n) { 440 | throw new Error('Not implemented'); 441 | } 442 | 443 | /** 444 | * Creates an array of integers from the specified start to end (inclusive) 445 | * 446 | * @param {number} start 447 | * @param {number} end 448 | * @return {array} 449 | * 450 | * @example 451 | * 1, 5 => [ 1, 2, 3, 4, 5 ] 452 | * -2, 2 => [ -2, -1, 0, 1, 2 ] 453 | * 0, 100 => [ 0, 1, 2, ..., 100 ] 454 | * 3, 3 => [ 3 ] 455 | */ 456 | function getIntervalArray(start, end) { 457 | throw new Error('Not implemented'); 458 | } 459 | 460 | /** 461 | * Returns array containing only unique values from the specified array. 462 | * 463 | * @param {array} arr 464 | * @return {array} 465 | * 466 | * @example 467 | * [ 1, 2, 3, 3, 2, 1 ] => [ 1, 2, 3 ] 468 | * [ 'a', 'a', 'a', 'a' ] => [ 'a' ] 469 | * [ 1, 1, 2, 2, 3, 3, 4, 4] => [ 1, 2, 3, 4] 470 | */ 471 | function distinct(arr) { 472 | throw new Error('Not implemented'); 473 | } 474 | 475 | /** 476 | * Groups elements of the specified array by key. 477 | * Returns multimap of keys extracted from array elements via keySelector callback 478 | * and values extracted via valueSelector callback. 479 | * See: https://en.wikipedia.org/wiki/Multimap 480 | * 481 | * @param {array} array 482 | * @param {Function} keySelector 483 | * @param {Function} valueSelector 484 | * @return {Map} 485 | * 486 | * @example 487 | * group([ 488 | * { country: 'Belarus', city: 'Brest' }, 489 | * { country: 'Russia', city: 'Omsk' }, 490 | * { country: 'Russia', city: 'Samara' }, 491 | * { country: 'Belarus', city: 'Grodno' }, 492 | * { country: 'Belarus', city: 'Minsk' }, 493 | * { country: 'Poland', city: 'Lodz' } 494 | * ], 495 | * item => item.country, 496 | * item => item.city 497 | * ) 498 | * => 499 | * Map { 500 | * "Belarus" => ["Brest", "Grodno", "Minsk"], 501 | * "Russia" => ["Omsk", "Samara"], 502 | * "Poland" => ["Lodz"] 503 | * } 504 | */ 505 | function group(array, keySelector, valueSelector) { 506 | throw new Error('Not implemented'); 507 | } 508 | 509 | 510 | /** 511 | * Projects each element of the specified array to a sequence and flattens the resulting sequences into one array. 512 | * 513 | * @param {array} arr 514 | * @param {Function} childrenSelector, a transform function to apply to each element that returns an array of children 515 | * @return {array} 516 | * 517 | * @example 518 | * [[1, 2], [3, 4], [5, 6]], (x) => x => [ 1, 2, 3, 4, 5, 6 ] 519 | * ['one','two','three'], x=>x.split('') => ['o','n','e','t','w','o','t','h','r','e','e'] 520 | */ 521 | function selectMany(arr, childrenSelector) { 522 | throw new Error('Not implemented'); 523 | } 524 | 525 | 526 | /** 527 | * Returns an element from the multidimentional array by the specified indexes. 528 | * 529 | * @param {array} arr 530 | * @param {array} indexes 531 | * @return {any} element from array 532 | * 533 | * @example 534 | * [[1, 2], [3, 4], [5, 6]], [0,0] => 1 (arr[0][0]) 535 | * ['one','two','three'], [2] => 'three' (arr[2]) 536 | * [[[ 1, 2, 3]]], [ 0, 0, 1 ] => 2 (arr[0][0][1]) 537 | */ 538 | function getElementByIndexes(arr, indexes) { 539 | throw new Error('Not implemented'); 540 | } 541 | 542 | 543 | /** 544 | * Swaps the head and tail of the specified array: 545 | * the head (first half) of array move to the end, the tail (last half) move to the start. 546 | * The middle element (if exists) leave on the same position. 547 | * 548 | * 549 | * @param {array} arr 550 | * @return {array} 551 | * 552 | * @example 553 | * [ 1, 2, 3, 4, 5 ] => [ 4, 5, 3, 1, 2 ] 554 | * \----/ \----/ 555 | * head tail 556 | * 557 | * [ 1, 2 ] => [ 2, 1 ] 558 | * [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 5, 6, 7, 8, 1, 2, 3, 4 ] 559 | * 560 | */ 561 | function swapHeadAndTail(arr) { 562 | throw new Error('Not implemented'); 563 | } 564 | 565 | 566 | module.exports = { 567 | findElement: findElement, 568 | generateOdds: generateOdds, 569 | doubleArray: doubleArray, 570 | getArrayOfPositives: getArrayOfPositives, 571 | getArrayOfStrings: getArrayOfStrings, 572 | removeFalsyValues: removeFalsyValues, 573 | getUpperCaseStrings: getUpperCaseStrings, 574 | getStringsLength: getStringsLength, 575 | insertItem: insertItem, 576 | getHead: getHead, 577 | getTail: getTail, 578 | toCsvText: toCsvText, 579 | toStringList: toStringList, 580 | toArrayOfSquares: toArrayOfSquares, 581 | getMovingSum: getMovingSum, 582 | getSecondItems: getSecondItems, 583 | propagateItemsByPositionIndex: propagateItemsByPositionIndex, 584 | get3TopItems: get3TopItems, 585 | getPositivesCount: getPositivesCount, 586 | sortDigitNamesByNumericOrder: sortDigitNamesByNumericOrder, 587 | getItemsSum: getItemsSum, 588 | getFalsyValuesCount: getFalsyValuesCount, 589 | findAllOccurences: findAllOccurences, 590 | sortCitiesArray: sortCitiesArray, 591 | getIdentityMatrix: getIdentityMatrix, 592 | getIntervalArray: getIntervalArray, 593 | distinct: distinct, 594 | group: group, 595 | selectMany: selectMany, 596 | getElementByIndexes: getElementByIndexes, 597 | swapHeadAndTail: swapHeadAndTail 598 | }; 599 | -------------------------------------------------------------------------------- /task/05-regex-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions * 7 | * * 8 | ********************************************************************************************/ 9 | 10 | 11 | /** 12 | * Returns the regexp that matches a GUID string representation 13 | * '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}', 14 | * where X is hexadecimal digit (0,1,2...,9,A,a,B,b,C,c,D,d,F,f) 15 | * 16 | * See more details: https://en.wikipedia.org/wiki/Globally_unique_identifier 17 | * 18 | * Match : 19 | * '{3F2504E0-4F89-41D3-9A0C-0305E82C3301}' 20 | * '{21EC2020-3AEA-4069-A2DD-08002B30309D}' 21 | * '{0c74f13f-fa83-4c48-9b33-68921dd72463}' 22 | * 23 | * Do not match: 24 | * '{D44EF4F4-280B47E5-91C7-261222A59621}' 25 | * '{D1A5279D-B27D-4CD4-A05E-EFDH53D08E8D}' 26 | * '{5EDEB36C-9006-467A8D04-AFB6F62CD7D2}' 27 | * '677E2553DD4D43B09DA77414DB1EB8EA' 28 | * '0c74f13f-fa83-4c48-9b33-68921dd72463' 29 | * 'The roof, the roof, the roof is on fire' 30 | * 31 | * @return {RegExp} 32 | */ 33 | function getRegexForGuid() { 34 | throw new Error('Not implemented'); 35 | } 36 | 37 | 38 | /** 39 | * Returns the regexp that matches all the strings from first column 40 | * but of them from the second 41 | * 42 | * Match : Do not match: 43 | * ----------- -------------- 44 | * 'pit' ' pt' 45 | * 'spot' 'Pot' 46 | * 'spate' 'peat' 47 | * 'slap two' 'part' 48 | * 'respite' 49 | * 50 | * NOTE : the regex lenth should be < 13 51 | * 52 | * @return {RegExp} 53 | * 54 | */ 55 | function getRegexForPitSpot() { 56 | throw new Error('Not implemented'); 57 | } 58 | 59 | 60 | /** 61 | * Returns the regexp that matches all IPv4 strings in 62 | * 'XX.XX.XX.XX' dotted format where XX is number 0 to 255 63 | * 64 | * Valid IPv4: Invalid IPv4 65 | * --------------- ----------------- 66 | * '0.0.0.0' '300.0.0.0' 67 | * '127.0.0.1' '127.0.0.-1' 68 | * '10.10.1.1' '23.24.25.26.27' 69 | * '46.61.155.237' 'Set dns to 8.8.8.8' 70 | * '010.234.015.001' 71 | * 72 | * @return {RegExp} 73 | */ 74 | function getRegexForIPv4() { 75 | throw new Error('Not implemented'); 76 | } 77 | 78 | 79 | /** 80 | * Returns the regexp that matches all SSN (Social Security Number) codes in 81 | * 'XXX-XX-XXXX' format where X is digit, where each group can't be all zeros 82 | * https://en.wikipedia.org/wiki/Social_Security_number 83 | * 84 | * Valid SSN: Invalid SSN 85 | * --------------- ----------------- 86 | * '123-45-6789' '123456789' 87 | * '234-56-2349' '000-56-2349' 88 | * '875-43-0298' '875-00-0298' 89 | * '034-01-0008' '034-01-0000' 90 | * '0S4-H1-HACK' 91 | * @return {RegExp} 92 | */ 93 | function getRegexForSSN() { 94 | throw new Error('Not implemented'); 95 | } 96 | 97 | 98 | /** 99 | * Returns the password validator regex. 100 | * Regex will validate a password to make sure it meets the follwing criteria: 101 | * - At least specified characters long (argument minLength) 102 | * - Contains a lowercase letter 103 | * - Contains an uppercase letter 104 | * - Contains a number 105 | * - Valid passwords will only be alphanumeric characters. 106 | * 107 | * @param {number} minLength 108 | * @return {Regex} 109 | * 110 | * @example 111 | * let validator = getPasswordValidator(6); 112 | * 'password'.match(validator) => false 113 | * 'Pa55Word'.match(validator) => true 114 | * 'PASSw0rd'.match(validator) => true 115 | * 'PASSW0RD'.match(validator) => false 116 | * 'Pa55'.match(validator) => false 117 | */ 118 | function getPasswordValidator(minLength) { 119 | throw new Error('Not implemented'); 120 | } 121 | 122 | 123 | module.exports = { 124 | getRegexForGuid: getRegexForGuid, 125 | getRegexForPitSpot: getRegexForPitSpot, 126 | getRegexForIPv4: getRegexForIPv4, 127 | getRegexForSSN: getRegexForSSN, 128 | getPasswordValidator: getPasswordValidator 129 | }; 130 | -------------------------------------------------------------------------------- /task/06-conditions-n-loops-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /************************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling * 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration * 8 | * * 9 | **************************************************************************************************/ 10 | 11 | 12 | /** 13 | * Returns the 'Fizz','Buzz' or an original number using the following rules: 14 | * 1) return original number 15 | * 2) but if number multiples of three return 'Fizz' 16 | * 3) for the multiples of five return 'Buzz' 17 | * 4) for numbers which are multiples of both three and five return 'FizzBuzz' 18 | * 19 | * @param {number} num 20 | * @return {any} 21 | * 22 | * @example 23 | * 2 => 2 24 | * 3 => 'Fizz' 25 | * 5 => 'Buzz' 26 | * 4 => 4 27 | * 15 => 'FizzBuzz' 28 | * 20 => 'Buzz' 29 | * 21 => 'Fizz' 30 | * 31 | */ 32 | function getFizzBuzz(num) { 33 | throw new Error('Not implemented'); 34 | } 35 | 36 | 37 | /** 38 | * Returns the factorial of the specified integer n. 39 | * 40 | * @param {number} n 41 | * @return {number} 42 | * 43 | * @example: 44 | * 1 => 1 45 | * 5 => 120 46 | * 10 => 3628800 47 | */ 48 | function getFactorial(n) { 49 | throw new Error('Not implemented'); 50 | } 51 | 52 | 53 | /** 54 | * Returns the sum of integer numbers between n1 and n2 (inclusive). 55 | * 56 | * @param {number} n1 57 | * @param {number} n2 58 | * @return {number} 59 | * 60 | * @example: 61 | * 1,2 => 3 ( = 1+2 ) 62 | * 5,10 => 45 ( = 5+6+7+8+9+10 ) 63 | * -1,1 => 0 ( = -1 + 0 + 1 ) 64 | */ 65 | function getSumBetweenNumbers(n1, n2) { 66 | throw new Error('Not implemented'); 67 | } 68 | 69 | 70 | /** 71 | * Returns true, if a triangle can be built with the specified sides a,b,c and false in any other ways. 72 | * 73 | * @param {number} a 74 | * @param {number} b 75 | * @param {number} c 76 | * @return {bool} 77 | * 78 | * @example: 79 | * 1,2,3 => false 80 | * 3,4,5 => true 81 | * 10,1,1 => false 82 | * 10,10,10 => true 83 | */ 84 | function isTriangle(a,b,c) { 85 | throw new Error('Not implemented'); 86 | } 87 | 88 | 89 | /** 90 | * Returns true, if two specified axis-aligned rectangles overlap, otherwise false. 91 | * Each rectangle representing by object 92 | * { 93 | * top: 5, 94 | * left: 5, 95 | * width: 20, 96 | * height: 10 97 | * } 98 | * 99 | * (5;5) 100 | * ------------- 101 | * | | 102 | * | | height = 10 103 | * ------------- 104 | * width=20 105 | * 106 | * NOTE: Please use canvas coordinate space (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes#The_grid), 107 | * it differs from Cartesian coordinate system. 108 | * 109 | * @param {object} rect1 110 | * @param {object} rect2 111 | * @return {bool} 112 | * 113 | * @example: 114 | * { top: 0, left: 0, width: 10, height: 10 }, 115 | * { top: 5, left: 5, width: 20, height: 20 } => true 116 | * 117 | * { top: 0, left: 0, width: 10, height: 10 }, 118 | * { top:20, left:20, width: 20, height: 20 } => false 119 | * 120 | */ 121 | function doRectanglesOverlap(rect1, rect2) { 122 | throw new Error('Not implemented'); 123 | } 124 | 125 | 126 | /** 127 | * Returns true, if point lies inside the circle, otherwise false. 128 | * Circle is an object of 129 | * { 130 | * center: { 131 | * x: 5, 132 | * y: 5 133 | * }, 134 | * radius: 20 135 | * } 136 | * 137 | * Point is object of 138 | * { 139 | * x: 5, 140 | * y: 5 141 | * } 142 | * 143 | * @param {object} circle 144 | * @param {object} point 145 | * @return {bool} 146 | * 147 | * @example: 148 | * { center: { x:0, y:0 }, radius:10 }, { x:0, y:0 } => true 149 | * { center: { x:0, y:0 }, radius:10 }, { x:10, y:10 } => false 150 | * 151 | */ 152 | function isInsideCircle(circle, point) { 153 | throw new Error('Not implemented'); 154 | } 155 | 156 | 157 | /** 158 | * Returns the first non repeated char in the specified strings otherwise returns null. 159 | * 160 | * @param {string} str 161 | * @return {string} 162 | * 163 | * @example: 164 | * 'The quick brown fox jumps over the lazy dog' => 'T' 165 | * 'abracadabra' => 'c' 166 | * 'entente' => null 167 | */ 168 | function findFirstSingleChar(str) { 169 | throw new Error('Not implemented'); 170 | } 171 | 172 | 173 | /** 174 | * Returns the string representation of math interval, specified by two points and include / exclude flags. 175 | * See the details: https://en.wikipedia.org/wiki/Interval_(mathematics) 176 | * 177 | * Please take attention, that the smaller number should be the first in the notation 178 | * 179 | * @param {number} a 180 | * @param {number} b 181 | * @param {bool} isStartIncluded 182 | * @param {bool} isEndIncluded 183 | * @return {string} 184 | * 185 | * @example 186 | * 0, 1, true, true => '[0, 1]' 187 | * 0, 1, true, false => '[0, 1)' 188 | * 0, 1, false, true => '(0, 1]' 189 | * 0, 1, false, false => '(0, 1)' 190 | * Smaller number has to be first : 191 | * 5, 3, true, true => '[3, 5]' 192 | * 193 | */ 194 | function getIntervalString(a, b, isStartIncluded, isEndIncluded) { 195 | throw new Error('Not implemented'); 196 | } 197 | 198 | 199 | /** 200 | * Reverse the specified string (put all chars in reverse order) 201 | * 202 | * @param {string} str 203 | * @return {string} 204 | * 205 | * @example: 206 | * 'The quick brown fox jumps over the lazy dog' => 'god yzal eht revo spmuj xof nworb kciuq ehT' 207 | * 'abracadabra' => 'arbadacarba' 208 | * 'rotator' => 'rotator' 209 | * 'noon' => 'noon' 210 | */ 211 | function reverseString(str) { 212 | throw new Error('Not implemented'); 213 | } 214 | 215 | 216 | /** 217 | * Reverse the specified integer number (put all digits in reverse order) 218 | * 219 | * @param {number} num 220 | * @return {number} 221 | * 222 | * @example: 223 | * 12345 => 54321 224 | * 1111 => 1111 225 | * 87354 => 45378 226 | * 34143 => 34143 227 | */ 228 | function reverseInteger(num) { 229 | throw new Error('Not implemented'); 230 | } 231 | 232 | 233 | /** 234 | * Validates the CCN (credit card number) and return true if CCN is valid 235 | * and false otherwise. 236 | * 237 | * See algorithm here : https://en.wikipedia.org/wiki/Luhn_algorithm 238 | * 239 | * @param {number} cnn 240 | * @return {boolean} 241 | * 242 | * @example: 243 | * 79927398713 => true 244 | * 4012888888881881 => true 245 | * 5123456789012346 => true 246 | * 378282246310005 => true 247 | * 371449635398431 => true 248 | * 249 | * 4571234567890111 => false 250 | * 5436468789016589 => false 251 | * 4916123456789012 => false 252 | */ 253 | function isCreditCardNumber(ccn) { 254 | throw new Error('Not implemented'); 255 | } 256 | 257 | 258 | /** 259 | * Returns the digital root of integer: 260 | * step1 : find sum of all digits 261 | * step2 : if sum > 9 then goto step1 otherwise return the sum 262 | * 263 | * @param {number} n 264 | * @return {number} 265 | * 266 | * @example: 267 | * 12345 ( 1+2+3+4+5 = 15, 1+5 = 6) => 6 268 | * 23456 ( 2+3+4+5+6 = 20, 2+0 = 2) => 2 269 | * 10000 ( 1+0+0+0+0 = 1 ) => 1 270 | * 165536 (1+6+5+5+3+6 = 26, 2+6 = 8) => 8 271 | */ 272 | function getDigitalRoot(num) { 273 | throw new Error('Not implemented'); 274 | } 275 | 276 | 277 | /** 278 | * Returns true if the specified string has the balanced brackets and false otherwise. 279 | * Balanced means that is, whether it consists entirely of pairs of opening/closing brackets 280 | * (in that order), none of which mis-nest. 281 | * Brackets include [],(),{},<> 282 | * 283 | * @param {string} str 284 | * @return {boolean} 285 | * 286 | * @example: 287 | * '' => true 288 | * '[]' => true 289 | * '{}' => true 290 | * '() => true 291 | * '[[]' => false 292 | * '][' => false 293 | * '[[][][[]]]' => true 294 | * '[[][]][' => false 295 | * '{)' = false 296 | * '{[(<{[]}>)]}' = true 297 | */ 298 | function isBracketsBalanced(str) { 299 | throw new Error('Not implemented'); 300 | } 301 | 302 | 303 | /** 304 | * Returns the human readable string of time period specified by the start and end time. 305 | * The result string should be constrcuted using the folliwing rules: 306 | * 307 | * --------------------------------------------------------------------- 308 | * Difference | Result 309 | * --------------------------------------------------------------------- 310 | * 0 to 45 seconds | a few seconds ago 311 | * 45 to 90 seconds | a minute ago 312 | * 90 seconds to 45 minutes | 2 minutes ago ... 45 minutes ago 313 | * 45 to 90 minutes | an hour ago 314 | * 90 minutes to 22 hours | 2 hours ago ... 22 hours ago 315 | * 22 to 36 hours | a day ago 316 | * 36 hours to 25 days | 2 days ago ... 25 days ago 317 | * 25 to 45 days | a month ago 318 | * 45 to 345 days | 2 months ago ... 11 months ago 319 | * 345 to 545 days (1.5 years) | a year ago 320 | * 546 days+ | 2 years ago ... 20 years ago 321 | * --------------------------------------------------------------------- 322 | * 323 | * @param {Date} startDate 324 | * @param {Date} endDate 325 | * @return {string} 326 | * 327 | * @example 328 | * Date('2000-01-01 01:00:00.100'), Date('2000-01-01 01:00:00.200') => 'a few seconds ago' 329 | * Date('2000-01-01 01:00:00.100'), Date('2000-01-01 01:00:05.000') => '5 minutes ago' 330 | * Date('2000-01-01 01:00:00.100'), Date('2000-01-02 03:00:05.000') => 'a day ago' 331 | * Date('2000-01-01 01:00:00.100'), Date('2015-01-02 03:00:05.000') => '15 years ago' 332 | * 333 | */ 334 | function timespanToHumanString(startDate, endDate) { 335 | throw new Error('Not implemented'); 336 | } 337 | 338 | 339 | /** 340 | * Returns the string with n-ary (binary, ternary, etc, where n<=10) representation of specified number. 341 | * See more about 342 | * https://en.wikipedia.org/wiki/Binary_number 343 | * https://en.wikipedia.org/wiki/Ternary_numeral_system 344 | * https://en.wikipedia.org/wiki/Radix 345 | * 346 | * @param {number} num 347 | * @param {number} n, radix of the result 348 | * @return {string} 349 | * 350 | * @example: 351 | * 1024, 2 => '10000000000' 352 | * 6561, 3 => '100000000' 353 | * 365, 2 => '101101101' 354 | * 365, 3 => '111112' 355 | * 365, 4 => '11231' 356 | * 365, 10 => '365' 357 | */ 358 | function toNaryString(num, n) { 359 | throw new Error('Not implemented'); 360 | } 361 | 362 | 363 | /** 364 | * Returns the commom directory path for specified array of full filenames. 365 | * 366 | * @param {array} pathes 367 | * @return {string} 368 | * 369 | * @example: 370 | * ['/web/images/image1.png', '/web/images/image2.png'] => '/web/images/' 371 | * ['/web/assets/style.css', '/web/scripts/app.js', 'home/setting.conf'] => '' 372 | * ['/web/assets/style.css', '/.bin/mocha', '/read.me'] => '/' 373 | * ['/web/favicon.ico', '/web-scripts/dump', '/webalizer/logs'] => '/' 374 | */ 375 | function getCommonDirectoryPath(pathes) { 376 | throw new Error('Not implemented'); 377 | } 378 | 379 | 380 | /** 381 | * Returns the product of two specified matrixes. 382 | * See details: https://en.wikipedia.org/wiki/Matrix_multiplication 383 | * 384 | * @param {array} m1 385 | * @param {array} m2 386 | * @return {array} 387 | * 388 | * @example: 389 | * [[ 1, 0, 0 ], [[ 1, 2, 3 ], [[ 1, 2, 3 ], 390 | * [ 0, 1, 0 ], X [ 4, 5, 6 ], => [ 4, 5, 6 ], 391 | * [ 0, 0, 1 ]] [ 7, 8, 9 ]] [ 7, 8, 9 ]] 392 | * 393 | * [[ 4 ], 394 | * [[ 1, 2, 3]] X [ 5 ], => [[ 32 ]] 395 | * [ 6 ]] 396 | * 397 | */ 398 | function getMatrixProduct(m1, m2) { 399 | throw new Error('Not implemented'); 400 | } 401 | 402 | 403 | /** 404 | * Returns the evaluation of the specified tic-tac-toe position. 405 | * See the details: https://en.wikipedia.org/wiki/Tic-tac-toe 406 | * 407 | * Position is provides as 3x3 array with the following values: 'X','0', undefined 408 | * Function should return who is winner in the current position according to the game rules. 409 | * The result can be: 'X','0',undefined 410 | * 411 | * @param {array} position 412 | * @return {string} 413 | * 414 | * @example 415 | * 416 | * [[ 'X', ,'0' ], 417 | * [ ,'X','0' ], => 'X' 418 | * [ , ,'X' ]] 419 | * 420 | * [[ '0','0','0' ], 421 | * [ ,'X', ], => '0' 422 | * [ 'X', ,'X' ]] 423 | * 424 | * [[ '0','X','0' ], 425 | * [ ,'X', ], => undefined 426 | * [ 'X','0','X' ]] 427 | * 428 | * [[ , , ], 429 | * [ , , ], => undefined 430 | * [ , , ]] 431 | * 432 | */ 433 | function evaluateTicTacToePosition(position) { 434 | throw new Error('Not implemented'); 435 | } 436 | 437 | 438 | module.exports = { 439 | getFizzBuzz: getFizzBuzz, 440 | getFactorial: getFactorial, 441 | getSumBetweenNumbers: getSumBetweenNumbers, 442 | isTriangle: isTriangle, 443 | doRectanglesOverlap: doRectanglesOverlap, 444 | isInsideCircle: isInsideCircle, 445 | findFirstSingleChar: findFirstSingleChar, 446 | getIntervalString : getIntervalString, 447 | reverseString: reverseString, 448 | reverseInteger: reverseInteger, 449 | isCreditCardNumber: isCreditCardNumber, 450 | getDigitalRoot: getDigitalRoot, 451 | isBracketsBalanced: isBracketsBalanced, 452 | timespanToHumanString : timespanToHumanString, 453 | toNaryString: toNaryString, 454 | getCommonDirectoryPath: getCommonDirectoryPath, 455 | getMatrixProduct: getMatrixProduct, 456 | evaluateTicTacToePosition : evaluateTicTacToePosition 457 | }; 458 | -------------------------------------------------------------------------------- /task/07-yield-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators * 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield * 8 | * * 9 | ********************************************************************************************/ 10 | 11 | 12 | /** 13 | * Returns the lines sequence of "99 Bottles of Beer" song: 14 | * 15 | * '99 bottles of beer on the wall, 99 bottles of beer.' 16 | * 'Take one down and pass it around, 98 bottles of beer on the wall.' 17 | * '98 bottles of beer on the wall, 98 bottles of beer.' 18 | * 'Take one down and pass it around, 97 bottles of beer on the wall.' 19 | * ... 20 | * '1 bottle of beer on the wall, 1 bottle of beer.' 21 | * 'Take one down and pass it around, no more bottles of beer on the wall.' 22 | * 'No more bottles of beer on the wall, no more bottles of beer.' 23 | * 'Go to the store and buy some more, 99 bottles of beer on the wall.' 24 | * 25 | * See the full text at 26 | * http://99-bottles-of-beer.net/lyrics.html 27 | * 28 | * NOTE: Please try to complete this task faster then original song finished: 29 | * https://www.youtube.com/watch?v=Z7bmyjxJuVY :) 30 | * 31 | * 32 | * @return {Iterable.} 33 | * 34 | */ 35 | function* get99BottlesOfBeer() { 36 | throw new Error('Not implemented'); 37 | } 38 | 39 | 40 | /** 41 | * Returns the Fibonacci sequence: 42 | * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, ... 43 | * 44 | * See more at: https://en.wikipedia.org/wiki/Fibonacci_number 45 | * 46 | * @return {Iterable.} 47 | * 48 | */ 49 | function* getFibonacciSequence() { 50 | throw new Error('Not implemented'); 51 | } 52 | 53 | 54 | /** 55 | * Traverses a tree using the depth-first strategy 56 | * See details: https://en.wikipedia.org/wiki/Depth-first_search 57 | * 58 | * Each node have child nodes in node.children array. 59 | * The leaf nodes do not have 'children' property. 60 | * 61 | * @params {object} root the tree root 62 | * @return {Iterable.} the sequence of all tree nodes in depth-first order 63 | * @example 64 | * 65 | * var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, 66 | * node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; 67 | * node1.children = [ node2, node6, node7 ]; 68 | * node2.children = [ node3, node4 ]; 69 | * node4.children = [ node5 ]; 70 | * node7.children = [ node8 ]; 71 | * 72 | * source tree (root = 1): 73 | * 1 74 | * / | \ 75 | * 2 6 7 76 | * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } 77 | * 3 4 8 78 | * | 79 | * 5 80 | * 81 | * depthTraversalTree(node1) => node1, node2, node3, node4, node5, node6, node7, node8 82 | * 83 | */ 84 | function* depthTraversalTree(root) { 85 | throw new Error('Not implemented'); 86 | } 87 | 88 | 89 | /** 90 | * Traverses a tree using the breadth-first strategy 91 | * See details: https://en.wikipedia.org/wiki/Breadth-first_search 92 | * 93 | * Each node have child nodes in node.children array. 94 | * The leaf nodes do not have 'children' property. 95 | * 96 | * @params {object} root the tree root 97 | * @return {Iterable.} the sequence of all tree nodes in breadth-first order 98 | * @example 99 | * source tree (root = 1): 100 | * 101 | * 1 102 | * / | \ 103 | * 2 3 4 104 | * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } 105 | * 5 6 7 106 | * | 107 | * 8 108 | * 109 | */ 110 | function* breadthTraversalTree(root) { 111 | throw new Error('Not implemented'); 112 | } 113 | 114 | 115 | /** 116 | * Merges two yield-style sorted sequences into the one sorted sequence. 117 | * The result sequence consists of sorted items from source iterators. 118 | * 119 | * @params {Iterable.} source1 120 | * @params {Iterable.} source2 121 | * @return {Iterable.} the merged sorted sequence 122 | * 123 | * @example 124 | * [ 1, 3, 5, ... ], [2, 4, 6, ... ] => [ 1, 2, 3, 4, 5, 6, ... ] 125 | * [ 0 ], [ 2, 4, 6, ... ] => [ 0, 2, 4, 6, ... ] 126 | * [ 1, 3, 5, ... ], [ -1 ] => [ -1, 1, 3, 5, ...] 127 | */ 128 | function* mergeSortedSequences(source1, source2) { 129 | throw new Error('Not implemented'); 130 | } 131 | 132 | 133 | module.exports = { 134 | get99BottlesOfBeer: get99BottlesOfBeer, 135 | getFibonacciSequence: getFibonacciSequence, 136 | depthTraversalTree: depthTraversalTree, 137 | breadthTraversalTree: breadthTraversalTree, 138 | mergeSortedSequences: mergeSortedSequences 139 | }; 140 | -------------------------------------------------------------------------------- /task/08-objects-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /************************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer * 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object * 8 | * * 9 | **************************************************************************************************/ 10 | 11 | 12 | /** 13 | * Returns the rectagle object with width and height parameters and getArea() method 14 | * 15 | * @param {number} width 16 | * @param {number} height 17 | * @return {Object} 18 | * 19 | * @example 20 | * var r = new Rectangle(10,20); 21 | * console.log(r.width); // => 10 22 | * console.log(r.height); // => 20 23 | * console.log(r.getArea()); // => 200 24 | */ 25 | function Rectangle(width, height) { 26 | throw new Error('Not implemented'); 27 | } 28 | 29 | 30 | /** 31 | * Returns the JSON representation of specified object 32 | * 33 | * @param {object} obj 34 | * @return {string} 35 | * 36 | * @example 37 | * [1,2,3] => '[1,2,3]' 38 | * { width: 10, height : 20 } => '{"height":10,"width":20}' 39 | */ 40 | function getJSON(obj) { 41 | throw new Error('Not implemented'); 42 | } 43 | 44 | 45 | /** 46 | * Returns the object of specified type from JSON representation 47 | * 48 | * @param {Object} proto 49 | * @param {string} json 50 | * @return {object} 51 | * 52 | * @example 53 | * var r = fromJSON(Rectangle.prototype, '{"width":10, "height":20}'); 54 | * 55 | */ 56 | function fromJSON(proto, json) { 57 | throw new Error('Not implemented'); 58 | } 59 | 60 | 61 | /** 62 | * Css selectors builder 63 | * 64 | * Each complex selector can consists of type, id, class, attribute, pseudo-class and pseudo-element selectors: 65 | * 66 | * element#id.class[attr]:pseudoClass::pseudoElement 67 | * \----/\----/\----------/ 68 | * Can be several occurences 69 | * 70 | * All types of selectors can be combined using the combinators ' ','+','~','>' . 71 | * 72 | * The task is to design a single class, independent classes or classes hierarchy and implement the functionality 73 | * to build the css selectors using the provided cssSelectorBuilder. 74 | * Each selector should have the stringify() method to output the string repsentation according to css specification. 75 | * 76 | * Provided cssSelectorBuilder should be used as facade only to create your own classes, 77 | * for example the first method of cssSelectorBuilder can be like this: 78 | * element: function(value) { 79 | * return new MySuperBaseElementSelector(...)... 80 | * }, 81 | * 82 | * The design of class(es) is totally up to you, but try to make it as simple, clear and readable as possible. 83 | * 84 | * @example 85 | * 86 | * var builder = cssSelectorBuilder; 87 | * 88 | * builder.id('main').class('container').class('editable').stringify() => '#main.container.editable' 89 | * 90 | * builder.element('a').attr('href$=".png"').pseudoClass('focus').stringify() => 'a[href$=".png"]:focus' 91 | * 92 | * builder.combine( 93 | * builder.element('div').id('main').class('container').class('draggable'), 94 | * '+', 95 | * builder.combine( 96 | * builder.element('table').id('data'), 97 | * '~', 98 | * builder.combine( 99 | * builder.element('tr').pseudoClass('nth-of-type(even)'), 100 | * ' ', 101 | * builder.element('td').pseudoClass('nth-of-type(even)') 102 | * ) 103 | * ) 104 | * ).stringify() => 'div#main.container.draggable + table#data ~ tr:nth-of-type(even) td:nth-of-type(even)' 105 | * 106 | * For more examples see unit tests. 107 | */ 108 | 109 | const cssSelectorBuilder = { 110 | 111 | element: function(value) { 112 | throw new Error('Not implemented'); 113 | }, 114 | 115 | id: function(value) { 116 | throw new Error('Not implemented'); 117 | }, 118 | 119 | class: function(value) { 120 | throw new Error('Not implemented'); 121 | }, 122 | 123 | attr: function(value) { 124 | throw new Error('Not implemented'); 125 | }, 126 | 127 | pseudoClass: function(value) { 128 | throw new Error('Not implemented'); 129 | }, 130 | 131 | pseudoElement: function(value) { 132 | throw new Error('Not implemented'); 133 | }, 134 | 135 | combine: function(selector1, combinator, selector2) { 136 | throw new Error('Not implemented'); 137 | }, 138 | }; 139 | 140 | 141 | module.exports = { 142 | Rectangle: Rectangle, 143 | getJSON: getJSON, 144 | fromJSON: fromJSON, 145 | cssSelectorBuilder: cssSelectorBuilder 146 | }; 147 | -------------------------------------------------------------------------------- /task/09-functions-n-closures-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /********************************************************************************************** 4 | * * 5 | * Plese read the following tutorial before implementing tasks: * 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions * 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function * 8 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments * 9 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures * 10 | * * 11 | **********************************************************************************************/ 12 | 13 | 14 | /** 15 | * Returns the functions composition of two specified functions f(x) and g(x). 16 | * The result of compose is to be a function of one argument, (lets call the argument x), 17 | * which works like applying function f to the result of applying function g to x, i.e. 18 | * getComposition(f,g)(x) = f(g(x)) 19 | * 20 | * @param {Function} f 21 | * @param {Function} g 22 | * @return {Function} 23 | * 24 | * @example 25 | * getComposition(Math.sin, Math.asin)(x) => Math.sin(Math.acos(x)) 26 | * 27 | */ 28 | function getComposition(f,g) { 29 | throw new Error('Not implemented'); 30 | } 31 | 32 | 33 | /** 34 | * Returns the math power function with the specified exponent 35 | * 36 | * @param {number} exponent 37 | * @return {Function} 38 | * 39 | * @example 40 | * var power2 = getPowerFunction(2); // => x^2 41 | * power2(2) => 4 42 | * power2(4) => 16 43 | * 44 | * var power05 = getPowerFunction(0.5); // => x^0.5 45 | * power05(4) => 2 46 | * power05(16) => 4 47 | * 48 | */ 49 | function getPowerFunction(exponent) { 50 | throw new Error('Not implemented'); 51 | } 52 | 53 | 54 | /** 55 | * Returns the polynom function of one argument based on specified coefficients. 56 | * See: https://en.wikipedia.org/wiki/Polynomial#Definition 57 | * 58 | * @params {integer} 59 | * @return {Function} 60 | * 61 | * @example 62 | * getPolynom(2,3,5) => y = 2*x^2 + 3*x + 5 63 | * getPolynom(1,-3) => y = x - 3 64 | * getPolynom(8) => y = 8 65 | * getPolynom() => null 66 | */ 67 | function getPolynom() { 68 | throw new Error('Not implemented'); 69 | } 70 | 71 | 72 | /** 73 | * Memoizes passed function and returns function 74 | * which invoked first time calls the passed function and then always returns cached result. 75 | * 76 | * @params {Function} func - function to memoize 77 | * @return {Function} memoized function 78 | * 79 | * @example 80 | * var memoizer = memoize(() => Math.random()); 81 | * memoizer() => some random number (first run, evaluates the result of Math.random()) 82 | * memoizer() => the same random number (second run, returns the previous cached result) 83 | * ... 84 | * memoizer() => the same random number (next run, returns the previous cached result) 85 | */ 86 | function memoize(func) { 87 | throw new Error('Not implemented'); 88 | } 89 | 90 | 91 | /** 92 | * Returns the function trying to call the passed function and if it throws, 93 | * retrying it specified number of attempts. 94 | * 95 | * @param {Function} func 96 | * @param {number} attempts 97 | * @return {Function} 98 | * 99 | * @example 100 | * var attempt = 0, retryer = retry(() => { 101 | * if (++attempt % 2) throw new Error('test'); 102 | * else return attempt; 103 | * }, 2); 104 | * retryer() => 2 105 | */ 106 | function retry(func, attempts) { 107 | throw new Error('Not implemented'); 108 | } 109 | 110 | 111 | /** 112 | * Returns the logging wrapper for the specified method, 113 | * Logger has to log the start and end of calling the specified function. 114 | * Logger has to log the arguments of invoked function. 115 | * The fromat of output log is: 116 | * (, ,...,) starts 117 | * (, ,...,) ends 118 | * 119 | * 120 | * @param {Function} func 121 | * @param {Function} logFunc - function to output log with single string argument 122 | * @return {Function} 123 | * 124 | * @example 125 | * 126 | * var cosLogger = logger(Math.cos, console.log); 127 | * var result = cosLogger(Math.PI)); // -1 128 | * 129 | * log from console.log: 130 | * cos(3.141592653589793) starts 131 | * cos(3.141592653589793) ends 132 | * 133 | */ 134 | function logger(func, logFunc) { 135 | throw new Error('Not implemented'); 136 | } 137 | 138 | 139 | /** 140 | * Return the function with partial applied arguments 141 | * 142 | * @param {Function} fn 143 | * @return {Function} 144 | * 145 | * @example 146 | * var fn = function(x1,x2,x3,x4) { return x1 + x2 + x3 + x4; }; 147 | * partialUsingArguments(fn, 'a')('b','c','d') => 'abcd' 148 | * partialUsingArguments(fn, 'a','b')('c','d') => 'abcd' 149 | * partialUsingArguments(fn, 'a','b','c')('d') => 'abcd' 150 | * partialUsingArguments(fn, 'a','b','c','d')() => 'abcd' 151 | */ 152 | function partialUsingArguments(fn) { 153 | throw new Error('Not implemented'); 154 | } 155 | 156 | 157 | /** 158 | * Returns the id generator function that returns next integer starting from specified number every time when invoking. 159 | * 160 | * @param {Number} startFrom 161 | * @return {Function} 162 | * 163 | * @example 164 | * var getId4 = getIdGenerator(4); 165 | * var getId10 = gerIdGenerator(10); 166 | * getId4() => 4 167 | * getId10() => 10 168 | * getId4() => 5 169 | * getId4() => 6 170 | * getId4() => 7 171 | * getId10() => 11 172 | */ 173 | function getIdGeneratorFunction(startFrom) { 174 | throw new Error('Not implemented'); 175 | } 176 | 177 | 178 | module.exports = { 179 | getComposition: getComposition, 180 | getPowerFunction: getPowerFunction, 181 | getPolynom: getPolynom, 182 | memoize: memoize, 183 | retry: retry, 184 | logger: logger, 185 | partialUsingArguments: partialUsingArguments, 186 | getIdGeneratorFunction: getIdGeneratorFunction, 187 | }; 188 | -------------------------------------------------------------------------------- /task/10-katas-1-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns the array of 32 compass points and heading. 5 | * See details here: 6 | * https://en.wikipedia.org/wiki/Points_of_the_compass#32_cardinal_points 7 | * 8 | * @return {array} 9 | * 10 | * Example of return : 11 | * [ 12 | * { abbreviation : 'N', azimuth : 0.00 , 13 | * { abbreviation : 'NbE', azimuth : 11.25 }, 14 | * { abbreviation : 'NNE', azimuth : 22.50 }, 15 | * ... 16 | * { abbreviation : 'NbW', azimuth : 348.75 } 17 | * ] 18 | */ 19 | function createCompassPoints() { 20 | throw new Error('Not implemented'); 21 | var sides = ['N','E','S','W']; // use array of cardinal directions only! 22 | } 23 | 24 | 25 | /** 26 | * Expand the braces of the specified string. 27 | * See https://en.wikipedia.org/wiki/Bash_(Unix_shell)#Brace_expansion 28 | * 29 | * In the input string, balanced pairs of braces containing comma-separated substrings 30 | * represent alternations that specify multiple alternatives which are to appear at that position in the output. 31 | * 32 | * @param {string} str 33 | * @return {Iterable.} 34 | * 35 | * NOTE: The order of output string does not matter. 36 | * 37 | * Example: 38 | * '~/{Downloads,Pictures}/*.{jpg,gif,png}' => '~/Downloads/*.jpg', 39 | * '~/Downloads/*.gif' 40 | * '~/Downloads/*.png', 41 | * '~/Pictures/*.jpg', 42 | * '~/Pictures/*.gif', 43 | * '~/Pictures/*.png' 44 | * 45 | * 'It{{em,alic}iz,erat}e{d,}, please.' => 'Itemized, please.', 46 | * 'Itemize, please.', 47 | * 'Italicized, please.', 48 | * 'Italicize, please.', 49 | * 'Iterated, please.', 50 | * 'Iterate, please.' 51 | * 52 | * 'thumbnail.{png,jp{e,}g}' => 'thumbnail.png' 53 | * 'thumbnail.jpeg' 54 | * 'thumbnail.jpg' 55 | * 56 | * 'nothing to do' => 'nothing to do' 57 | */ 58 | function* expandBraces(str) { 59 | throw new Error('Not implemented'); 60 | } 61 | 62 | 63 | /** 64 | * Returns the ZigZag matrix 65 | * 66 | * The fundamental idea in the JPEG compression algorithm is to sort coefficient of given image by zigzag path and encode it. 67 | * In this task you are asked to implement a simple method to create a zigzag square matrix. 68 | * See details at https://en.wikipedia.org/wiki/JPEG#Entropy_coding 69 | * and zigzag path here: https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/JPEG_ZigZag.svg/220px-JPEG_ZigZag.svg.png 70 | * 71 | * @param {number} n - matrix dimension 72 | * @return {array} n x n array of zigzag path 73 | * 74 | * @example 75 | * 1 => [[0]] 76 | * 77 | * 2 => [[ 0, 1 ], 78 | * [ 2, 3 ]] 79 | * 80 | * [[ 0, 1, 5 ], 81 | * 3 => [ 2, 4, 6 ], 82 | * [ 3, 7, 8 ]] 83 | * 84 | * [[ 0, 1, 5, 6 ], 85 | * 4 => [ 2, 4, 7,12 ], 86 | * [ 3, 8,11,13 ], 87 | * [ 9,10,14,15 ]] 88 | * 89 | */ 90 | function getZigZagMatrix(n) { 91 | throw new Error('Not implemented'); 92 | } 93 | 94 | 95 | /** 96 | * Returns true if specified subset of dominoes can be placed in a row accroding to the game rules. 97 | * Dominoes details see at: https://en.wikipedia.org/wiki/Dominoes 98 | * 99 | * Each domino tile presented as an array [x,y] of tile value. 100 | * For example, the subset [1, 1], [2, 2], [1, 2] can be arranged in a row (as [1, 1] followed by [1, 2] followed by [2, 2]), 101 | * while the subset [1, 1], [0, 3], [1, 4] can not be arranged in one row. 102 | * NOTE that as in usual dominoes playing any pair [i, j] can also be treated as [j, i]. 103 | * 104 | * @params {array} dominoes 105 | * @return {bool} 106 | * 107 | * @example 108 | * 109 | * [[0,1], [1,1]] => true 110 | * [[1,1], [2,2], [1,5], [5,6], [6,3]] => false 111 | * [[1,3], [2,3], [1,4], [2,4], [1,5], [2,5]] => true 112 | * [[0,0], [0,1], [1,1], [0,2], [1,2], [2,2], [0,3], [1,3], [2,3], [3,3]] => false 113 | * 114 | */ 115 | function canDominoesMakeRow(dominoes) { 116 | throw new Error('Not implemented'); 117 | } 118 | 119 | 120 | /** 121 | * Returns the string expression of the specified ordered list of integers. 122 | * 123 | * A format for expressing an ordered list of integers is to use a comma separated list of either: 124 | * - individual integers 125 | * - or a range of integers denoted by the starting integer separated from the end integer in the range by a dash, '-'. 126 | * (The range includes all integers in the interval including both endpoints) 127 | * The range syntax is to be used only for, and for every range that expands to more than two values. 128 | * 129 | * @params {array} nums 130 | * @return {bool} 131 | * 132 | * @example 133 | * 134 | * [ 0, 1, 2, 3, 4, 5 ] => '0-5' 135 | * [ 1, 4, 5 ] => '1,4,5' 136 | * [ 0, 1, 2, 5, 7, 8, 9] => '0-2,5,7-9' 137 | * [ 1, 2, 4, 5] => '1,2,4,5' 138 | */ 139 | function extractRanges(nums) { 140 | throw new Error('Not implemented'); 141 | } 142 | 143 | module.exports = { 144 | createCompassPoints : createCompassPoints, 145 | expandBraces : expandBraces, 146 | getZigZagMatrix : getZigZagMatrix, 147 | canDominoesMakeRow : canDominoesMakeRow, 148 | extractRanges : extractRanges 149 | }; 150 | -------------------------------------------------------------------------------- /task/11-katas-2-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns the bank account number parsed from specified string. 5 | * 6 | * You work for a bank, which has recently purchased an ingenious machine to assist in reading letters and faxes sent in by branch offices. 7 | * The machine scans the paper documents, and produces a string with a bank account that looks like this: 8 | * 9 | * _ _ _ _ _ _ _ 10 | * | _| _||_||_ |_ ||_||_| 11 | * ||_ _| | _||_| ||_| _| 12 | * 13 | * Each string contains an account number written using pipes and underscores. 14 | * Each account number should have 9 digits, all of which should be in the range 0-9. 15 | * 16 | * Your task is to write a function that can take bank account string and parse it into actual account numbers. 17 | * 18 | * @param {string} bankAccount 19 | * @return {number} 20 | * 21 | * Example of return : 22 | * 23 | * ' _ _ _ _ _ _ _ \n'+ 24 | * ' | _| _||_||_ |_ ||_||_|\n'+ => 123456789 25 | * ' ||_ _| | _||_| ||_| _|\n' 26 | * 27 | * ' _ _ _ _ _ _ _ _ _ \n'+ 28 | * '| | _| _|| ||_ |_ ||_||_|\n'+ => 23056789 29 | * '|_||_ _||_| _||_| ||_| _|\n', 30 | * 31 | * ' _ _ _ _ _ _ _ _ _ \n'+ 32 | * '|_| _| _||_||_ |_ |_||_||_|\n'+ => 823856989 33 | * '|_||_ _||_| _||_| _||_| _|\n', 34 | * 35 | */ 36 | function parseBankAccount(bankAccount) { 37 | throw new Error('Not implemented'); 38 | } 39 | 40 | 41 | /** 42 | * Returns the string, but with line breaks inserted at just the right places to make sure that no line is longer than the specified column number. 43 | * Lines can be broken at word boundaries only. 44 | * 45 | * @param {string} text 46 | * @param {number} columns 47 | * @return {Iterable.} 48 | * 49 | * @example : 50 | * 51 | * 'The String global object is a constructor for strings, or a sequence of characters.', 26 => 'The String global object', 52 | * 'is a constructor for', 53 | * 'strings, or a sequence of', 54 | * 'characters.' 55 | * 56 | * 'The String global object is a constructor for strings, or a sequence of characters.', 12 => 'The String', 57 | * 'global', 58 | * 'object is a', 59 | * 'constructor', 60 | * 'for strings,', 61 | * 'or a', 62 | * 'sequence of', 63 | * 'characters.' 64 | */ 65 | function* wrapText(text, columns) { 66 | throw new Error('Not implemented'); 67 | } 68 | 69 | 70 | /** 71 | * Returns the rank of the specified poker hand. 72 | * See the ranking rules here: https://en.wikipedia.org/wiki/List_of_poker_hands. 73 | * 74 | * @param {array} hand 75 | * @return {PokerRank} rank 76 | * 77 | * @example 78 | * [ '4♥','5♥','6♥','7♥','8♥' ] => PokerRank.StraightFlush 79 | * [ 'A♠','4♠','3♠','5♠','2♠' ] => PokerRank.StraightFlush 80 | * [ '4♣','4♦','4♥','4♠','10♥' ] => PokerRank.FourOfKind 81 | * [ '4♣','4♦','5♦','5♠','5♥' ] => PokerRank.FullHouse 82 | * [ '4♣','5♣','6♣','7♣','Q♣' ] => PokerRank.Flush 83 | * [ '2♠','3♥','4♥','5♥','6♥' ] => PokerRank.Straight 84 | * [ '2♥','4♦','5♥','A♦','3♠' ] => PokerRank.Straight 85 | * [ '2♥','2♠','2♦','7♥','A♥' ] => PokerRank.ThreeOfKind 86 | * [ '2♥','4♦','4♥','A♦','A♠' ] => PokerRank.TwoPairs 87 | * [ '3♥','4♥','10♥','3♦','A♠' ] => PokerRank.OnePair 88 | * [ 'A♥','K♥','Q♥','2♦','3♠' ] => PokerRank.HighCard 89 | */ 90 | const PokerRank = { 91 | StraightFlush: 8, 92 | FourOfKind: 7, 93 | FullHouse: 6, 94 | Flush: 5, 95 | Straight: 4, 96 | ThreeOfKind: 3, 97 | TwoPairs: 2, 98 | OnePair: 1, 99 | HighCard: 0 100 | } 101 | 102 | function getPokerHandRank(hand) { 103 | throw new Error('Not implemented'); 104 | } 105 | 106 | 107 | /** 108 | * Returns the rectangles sequence of specified figure. 109 | * The figure is ASCII multiline string comprised of minus signs -, plus signs +, vertical bars | and whitespaces. 110 | * The task is to break the figure in the rectangles it is made of. 111 | * 112 | * NOTE: The order of rectanles does not matter. 113 | * 114 | * @param {string} figure 115 | * @return {Iterable.} decomposition to basic parts 116 | * 117 | * @example 118 | * 119 | * '+------------+\n'+ 120 | * '| |\n'+ 121 | * '| |\n'+ '+------------+\n'+ 122 | * '| |\n'+ '| |\n'+ '+------+\n'+ '+-----+\n'+ 123 | * '+------+-----+\n'+ => '| |\n'+ , '| |\n'+ , '| |\n'+ 124 | * '| | |\n'+ '| |\n'+ '| |\n'+ '| |\n'+ 125 | * '| | |\n' '+------------+\n' '+------+\n' '+-----+\n' 126 | * '+------+-----+\n' 127 | * 128 | * 129 | * 130 | * ' +-----+ \n'+ 131 | * ' | | \n'+ '+-------------+\n'+ 132 | * '+--+-----+----+\n'+ '+-----+\n'+ '| |\n'+ 133 | * '| |\n'+ => '| |\n'+ , '| |\n'+ 134 | * '| |\n'+ '+-----+\n' '+-------------+\n' 135 | * '+-------------+\n' 136 | */ 137 | function* getFigureRectangles(figure) { 138 | throw new Error('Not implemented'); 139 | } 140 | 141 | 142 | module.exports = { 143 | parseBankAccount : parseBankAccount, 144 | wrapText: wrapText, 145 | PokerRank: PokerRank, 146 | getPokerHandRank: getPokerHandRank, 147 | getFigureRectangles: getFigureRectangles 148 | }; 149 | -------------------------------------------------------------------------------- /task/12-katas-3-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Returns true if word occurrs in the specified word snaking puzzle. 5 | * Each words can be constructed using "snake" path inside a grid with top, left, right and bottom directions. 6 | * Each char can be used only once ("snake" should not cross itself). 7 | * 8 | * @param {array} puzzle 9 | * @param {array} searchStr 10 | * @return {bool} 11 | * 12 | * @example 13 | * var puzzle = [ 14 | * 'ANGULAR', 15 | * 'REDNCAE', 16 | * 'RFIDTCL', 17 | * 'AGNEGSA', 18 | * 'YTIRTSP', 19 | * ]; 20 | * 'ANGULAR' => true (first row) 21 | * 'REACT' => true (starting from the top-right R adn follow the ↓ ← ← ↓ ) 22 | * 'UNDEFINED' => true 23 | * 'RED' => true 24 | * 'STRING' => true 25 | * 'CLASS' => true 26 | * 'ARRAY' => true (first column) 27 | * 'FUNCTION' => false 28 | * 'NULL' => false 29 | */ 30 | function findStringInSnakingPuzzle(puzzle, searchStr) { 31 | throw new Error('Not implemented'); 32 | } 33 | 34 | 35 | /** 36 | * Returns all permutations of the specified string. 37 | * Assume all chars in the specified string are different. 38 | * The order of permutations does not matter. 39 | * 40 | * @param {string} chars 41 | * @return {Iterable.} all posible strings constructed with the chars from the specfied string 42 | * 43 | * @example 44 | * 'ab' => 'ab','ba' 45 | * 'abc' => 'abc','acb','bac','bca','cab','cba' 46 | */ 47 | function* getPermutations(chars) { 48 | throw new Error('Not implemented'); 49 | } 50 | 51 | 52 | /** 53 | * Returns the most profit from stock quotes. 54 | * Stock quotes are stores in an array in order of date. 55 | * The stock profit is the difference in prices in buying and selling stock. 56 | * Each day, you can either buy one unit of stock, sell any number of stock units you have already bought, or do nothing. 57 | * Therefore, the most profit is the maximum difference of all pairs in a sequence of stock prices. 58 | * 59 | * @param {array} quotes 60 | * @return {number} max profit 61 | * 62 | * @example 63 | * [ 1, 2, 3, 4, 5, 6] => 15 (buy at 1,2,3,4,5 and then sell all at 6) 64 | * [ 6, 5, 4, 3, 2, 1] => 0 (nothing to buy) 65 | * [ 1, 6, 5, 10, 8, 7 ] => 18 (buy at 1,6,5 and sell all at 10) 66 | */ 67 | function getMostProfitFromStockQuotes(quotes) { 68 | throw new Error('Not implemented'); 69 | } 70 | 71 | 72 | /** 73 | * Class representing the url shorting helper. 74 | * Feel free to implement any algorithm, but do not store link in the key\value stores. 75 | * The short link can be at least 1.5 times shorter than the original url. 76 | * 77 | * @class 78 | * 79 | * @example 80 | * 81 | * var urlShortener = new UrlShortener(); 82 | * var shortLink = urlShortener.encode('https://en.wikipedia.org/wiki/URL_shortening'); 83 | * var original = urlShortener.decode(shortLink); // => 'https://en.wikipedia.org/wiki/URL_shortening' 84 | * 85 | */ 86 | function UrlShortener() { 87 | this.urlAllowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ 88 | "abcdefghijklmnopqrstuvwxyz"+ 89 | "0123456789-_.~!*'();:@&=+$,/?#[]"; 90 | } 91 | 92 | UrlShortener.prototype = { 93 | 94 | encode: function(url) { 95 | throw new Error('Not implemented'); 96 | }, 97 | 98 | decode: function(code) { 99 | throw new Error('Not implemented'); 100 | } 101 | } 102 | 103 | 104 | module.exports = { 105 | findStringInSnakingPuzzle: findStringInSnakingPuzzle, 106 | getPermutations: getPermutations, 107 | getMostProfitFromStockQuotes: getMostProfitFromStockQuotes, 108 | UrlShortener: UrlShortener 109 | }; 110 | -------------------------------------------------------------------------------- /test/01-strings-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/01-strings-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('01-strings-tasks', function() { 8 | 9 | it.optional('concatenateStrings should return concatenation of two strings', function() { 10 | assert.equal('aabb', tasks.concatenateStrings('aa','bb')); 11 | assert.equal('aa', tasks.concatenateStrings('aa','')); 12 | assert.equal('bb', tasks.concatenateStrings('','bb')); 13 | }); 14 | 15 | it.optional('getStringLength should return the length of string', function() { 16 | assert.equal(5, tasks.getStringLength('aaaaa'), "'aaaaa' length should be 5"); 17 | assert.equal(0, tasks.getStringLength(''), "'' length should be 0"); 18 | }); 19 | 20 | it.optional('getStringFromTemplate should create a string from template using given parameters', function() { 21 | assert.equal('Hello, John Doe!', tasks.getStringFromTemplate('John','Doe')); 22 | assert.equal('Hello, Chuck Norris!', tasks.getStringFromTemplate('Chuck','Norris')); 23 | }); 24 | 25 | it.optional('getFirstChar should return the first char from given string', function() { 26 | assert.equal('J', tasks.getFirstChar('John Doe')); 27 | assert.equal('c', tasks.getFirstChar('cat')); 28 | }); 29 | 30 | it.optional('extractNameFromTemplate should parse the name from given string', function() { 31 | assert.equal('John Doe', tasks.extractNameFromTemplate('Hello, John Doe!')); 32 | assert.equal('Chuck Norris', tasks.extractNameFromTemplate('Hello, Chuck Norris!')); 33 | }); 34 | 35 | it.optional('removeLeadingAndTrailingWhitespaces should remove leading and trailing whitespaces from the string', function() { 36 | assert.equal('Abracadabra', tasks.removeLeadingAndTrailingWhitespaces(' Abracadabra')); 37 | assert.equal('cat', tasks.removeLeadingAndTrailingWhitespaces('cat')); 38 | assert.equal('Hello, World!', tasks.removeLeadingAndTrailingWhitespaces('\tHello, World! ')); 39 | }); 40 | 41 | it.optional('repeatString should repeat string specified number of times', function() { 42 | assert.equal('AAAAA', tasks.repeatString('A', 5)); 43 | assert.equal('catcatcat', tasks.repeatString('cat', 3)); 44 | }); 45 | 46 | it.optional('removeFirstOccurrences should remove all specified values from a string', function() { 47 | assert.equal('To be or to be', tasks.removeFirstOccurrences('To be or not to be', ' not')); 48 | assert.equal('I like legs', tasks.removeFirstOccurrences('I like legends', 'end')); 49 | assert.equal('ABAB', tasks.removeFirstOccurrences('ABABAB','BA')); 50 | }); 51 | 52 | it.optional('unbracketTag should remove first and last angle brackets from tag string', function() { 53 | assert.equal('div', tasks.unbracketTag('
')); 54 | assert.equal('span', tasks.unbracketTag('')); 55 | assert.equal('a', tasks.unbracketTag('')); 56 | }); 57 | 58 | it.optional('convertToUpperCase should convert all chars from specified string into upper case', function() { 59 | assert.equal('THUNDERSTRUCK', tasks.convertToUpperCase('Thunderstruck')); 60 | assert.equal('ABCDEFGHIJKLMNOPQRSTUVWXYZ', tasks.convertToUpperCase('abcdefghijklmnopqrstuvwxyz')); 61 | }); 62 | 63 | it.optional('extractEmails should extract emails from string list delimeted by semicolons', function() { 64 | assert.deepEqual( 65 | ['angus.young@gmail.com', 'brian.johnson@hotmail.com', 'bon.scott@yahoo.com'], 66 | tasks.extractEmails('angus.young@gmail.com;brian.johnson@hotmail.com;bon.scott@yahoo.com') 67 | ); 68 | assert.deepEqual( 69 | ['info@gmail.com'], 70 | tasks.extractEmails('info@gmail.com') 71 | ); 72 | }); 73 | 74 | it.optional('getRectangleString should return the string reprentation of rectangle with specified size', function() { 75 | assert.equal( 76 | '┌────┐\n'+ 77 | '│ │\n'+ 78 | '│ │\n'+ 79 | '└────┘\n', 80 | tasks.getRectangleString(6, 4) 81 | ); 82 | assert.deepEqual( 83 | '┌┐\n'+ 84 | '└┘\n', 85 | tasks.getRectangleString(2, 2) 86 | ); 87 | assert.deepEqual( 88 | '┌──────────┐\n'+ 89 | '│ │\n'+ 90 | '└──────────┘\n', 91 | tasks.getRectangleString(12, 3) 92 | ); 93 | }); 94 | 95 | it.optional('encodeToRot13 should encode-decode string using ROT13 algorithm', function() { 96 | assert.equal('uryyb', tasks.encodeToRot13('hello')); 97 | assert.equal('Jul qvq gur puvpxra pebff gur ebnq?', tasks.encodeToRot13('Why did the chicken cross the road?')); 98 | assert.equal('To get to the other side!', tasks.encodeToRot13('Gb trg gb gur bgure fvqr!')); 99 | assert.equal( 100 | 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm', 101 | tasks.encodeToRot13('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') 102 | ); 103 | }); 104 | 105 | it.optional('isString should return true if argument ia a string', function() { 106 | assert.equal(false, tasks.isString(), "undefined"); 107 | assert.equal(false, tasks.isString(null), "null"); 108 | assert.equal(false, tasks.isString([]), "[]"); 109 | assert.equal(true, tasks.isString('test'), "test"); 110 | assert.equal(true, tasks.isString(new String('test')), "new String('test')"); 111 | }); 112 | 113 | it.optional('getCardId should return the index of card in the initial deck', function() { 114 | [ 115 | 'A♣','2♣','3♣','4♣','5♣','6♣','7♣','8♣','9♣','10♣','J♣','Q♣','K♣', 116 | 'A♦','2♦','3♦','4♦','5♦','6♦','7♦','8♦','9♦','10♦','J♦','Q♦','K♦', 117 | 'A♥','2♥','3♥','4♥','5♥','6♥','7♥','8♥','9♥','10♥','J♥','Q♥','K♥', 118 | 'A♠','2♠','3♠','4♠','5♠','6♠','7♠','8♠','9♠','10♠','J♠','Q♠','K♠' 119 | ].forEach((val, index) => { 120 | assert.equal( 121 | index, 122 | tasks.getCardId(val), 123 | `Invalid id for card '${val}':` 124 | ) 125 | }); 126 | 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/02-numbers-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/02-numbers-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('02-numbers-tasks', function() { 8 | 9 | it.optional('getRectangleArea should return a square of rectangle', function() { 10 | assert.equal(50, tasks.getRectangleArea(5, 10)); 11 | assert.equal(25, tasks.getRectangleArea(5, 5)); 12 | }); 13 | 14 | it.optional('getCicleCircumference should return a circumference of cicle', function() { 15 | assert.equal(31.41592653589793, tasks.getCicleCircumference(5)); 16 | assert.equal(19.729201864543903, tasks.getCicleCircumference(3.14)); 17 | assert.equal(0, tasks.getCicleCircumference(0)); 18 | }); 19 | 20 | it.optional('getAverage should return an average of two numbers', function() { 21 | assert.equal(5, tasks.getAverage(5, 5)); 22 | assert.equal(5, tasks.getAverage(10, 0)); 23 | assert.equal(0, tasks.getAverage(-3, 3)); 24 | assert.equal(Number.MAX_VALUE-1, tasks.getAverage(Number.MAX_VALUE-2, Number.MAX_VALUE)); 25 | assert.equal(Number.MAX_VALUE / 4, tasks.getAverage(Number.MAX_VALUE, -Number.MAX_VALUE / 2)); 26 | }); 27 | 28 | it.optional('getDistanceBetweenPoints should return a distance between points', function() { 29 | assert.equal(1, tasks.getDistanceBetweenPoints(0, 0, 0, 1)); 30 | assert.equal(1, tasks.getDistanceBetweenPoints(0, 0, 1, 0)); 31 | assert.equal(18.027756377319946, tasks.getDistanceBetweenPoints(-5, 0, 10, -10)); 32 | }); 33 | 34 | it.optional('getLinearEquationRoot should return a root of linear equation', function() { 35 | assert.equal(2, tasks.getLinearEquationRoot(5, -10)); 36 | assert.equal(-8, tasks.getLinearEquationRoot(1, 8)); 37 | assert.equal(0, tasks.getLinearEquationRoot(5, 0)); 38 | }); 39 | 40 | it.optional('getAngleBetweenVectors should return a angle (in radians) between two linear vectors', function() { 41 | assert.equal(Math.PI/2, tasks.getAngleBetweenVectors(1, 0, 0, 1)); 42 | assert.equal(Math.PI, tasks.getAngleBetweenVectors(0, 1, 0, -1)); 43 | assert.equal(Math.PI/2, tasks.getAngleBetweenVectors(0, -1, 1, 0)); 44 | assert.equal(0, tasks.getAngleBetweenVectors(0, 1, 0, 1)); 45 | }); 46 | 47 | it.optional('getLastDigit should return a last digit of the number', function() { 48 | assert.equal(0, tasks.getLastDigit(100)); 49 | assert.equal(7, tasks.getLastDigit(37)); 50 | assert.equal(5, tasks.getLastDigit(5)); 51 | assert.equal(0, tasks.getLastDigit(0)); 52 | }); 53 | 54 | it.optional('parseNumberFromString should return a number from the given string representation', function() { 55 | assert.equal(100, tasks.parseNumberFromString('100')); 56 | assert.equal(37, tasks.parseNumberFromString('37')); 57 | assert.equal(-525.5, tasks.parseNumberFromString('-525.5')); 58 | }); 59 | 60 | it.optional('getParallelipidedDiagonal should return a diagonal length of the rectagular parallepiped', function() { 61 | assert.equal(Math.sqrt(3), tasks.getParallelipidedDiagonal(1,1,1)); 62 | assert.equal(Math.sqrt(27), tasks.getParallelipidedDiagonal(3,3,3)); 63 | //assert.equal(Math.sqrt(14), tasks.getParallelipidedDiagonal(1,2,3)); 64 | }); 65 | 66 | it.optional('roundToPowerOfTen should return an number rounded to specified power of 10', function() { 67 | assert.equal(1234, tasks.roundToPowerOfTen(1234,0)); 68 | assert.equal(1230, tasks.roundToPowerOfTen(1234,1)); 69 | assert.equal(1200, tasks.roundToPowerOfTen(1234,2)); 70 | assert.equal(1000, tasks.roundToPowerOfTen(1234,3)); 71 | 72 | assert.equal(9678, tasks.roundToPowerOfTen(9678,0)); 73 | assert.equal(9680, tasks.roundToPowerOfTen(9678,1)); 74 | assert.equal(9700, tasks.roundToPowerOfTen(9678,2)); 75 | assert.equal(10000, tasks.roundToPowerOfTen(9678,3)); 76 | }); 77 | 78 | it.optional('isPrime should return true if specified number is prime', function() { 79 | assert.equal(true, tasks.isPrime(2), "2"); 80 | assert.equal(true, tasks.isPrime(3), "3"); 81 | assert.equal(false, tasks.isPrime(4), "4"); 82 | assert.equal(true, tasks.isPrime(5), "5"); 83 | assert.equal(false, tasks.isPrime(6), "6"); 84 | assert.equal(true, tasks.isPrime(7), "7"); 85 | assert.equal(false, tasks.isPrime(8), "8"); 86 | assert.equal(false, tasks.isPrime(9), "9"); 87 | assert.equal(false, tasks.isPrime(10), "10"); 88 | assert.equal(true, tasks.isPrime(11), "11"); 89 | assert.equal(false, tasks.isPrime(12), "12"); 90 | assert.equal(true, tasks.isPrime(13), "13"); 91 | assert.equal(true, tasks.isPrime(113), "113"); 92 | assert.equal(false, tasks.isPrime(119), "119"); 93 | }); 94 | 95 | it.optional('toNumber should convert any value to number or return the default', function() { 96 | assert.equal(0, tasks.toNumber(null, 0)); 97 | assert.equal(0, tasks.toNumber('test', 0)); 98 | assert.equal(1, tasks.toNumber('1', 0)); 99 | assert.equal(42, tasks.toNumber(42, 0)); 100 | assert.equal(42, tasks.toNumber(new Number(42), 0)); 101 | assert.equal(-1, tasks.toNumber(undefined, -1)); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/03-date-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/03-date-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('03-date-tasks', function() { 8 | 9 | it.optional('parseDataFromRfc2822 should parse rfc2822 string into a date value', function () { 10 | assert.equal( 11 | new Date(1995, 11, 17, 3, 24, 0).valueOf(), 12 | tasks.parseDataFromRfc2822('December 17, 1995 03:24:00').valueOf() 13 | ); 14 | 15 | assert.equal( 16 | 1453816082000, 17 | tasks.parseDataFromRfc2822('Tue, 26 Jan 2016 13:48:02 GMT').valueOf() 18 | ); 19 | 20 | assert.equal( 21 | 895370400000, 22 | tasks.parseDataFromRfc2822('Sun, 17 May 1998 03:00:00 GMT+0100').valueOf() 23 | ); 24 | }); 25 | 26 | 27 | it.optional('parseDataFromIso8601 should parse ISO 8601 string into a date value', function () { 28 | assert.equal( 29 | 1453219657000, 30 | tasks.parseDataFromIso8601('2016-01-19T16:07:37+00:00').valueOf() 31 | ); 32 | 33 | assert.equal( 34 | 1453190857000, 35 | tasks.parseDataFromIso8601('2016-01-19T08:07:37Z').valueOf() 36 | ); 37 | }); 38 | 39 | 40 | it.optional('isLeapYear should true if specified year is leap', function () { 41 | [ 42 | new Date(2000,1,1), 43 | new Date(2012,1,1) 44 | ].forEach(date => { 45 | assert( 46 | tasks.isLeapYear(date) == true, 47 | `${date} is a leap year` 48 | ); 49 | }); 50 | 51 | [ 52 | new Date(1900,1,1), 53 | new Date(2001,1,1) 54 | ].forEach(date => { 55 | assert( 56 | tasks.isLeapYear(date) == false, 57 | `${date} is not a leap year` 58 | ); 59 | }); 60 | 61 | }); 62 | 63 | 64 | it.optional('timeSpanToString should return the string represation of time span between two dates', function () { 65 | [ 66 | { 67 | startDate: new Date(2000,1,1,10,0,0), 68 | endDate: new Date(2000,1,1,11,0,0), 69 | expected: '01:00:00.000' 70 | }, { 71 | startDate: new Date(2000,1,1,10,0,0), 72 | endDate: new Date(2000,1,1,10,30,0), 73 | expected: '00:30:00.000' 74 | }, { 75 | startDate: new Date(2000,1,1,10,0,0), 76 | endDate: new Date(2000,1,1,10,0,20), 77 | expected: '00:00:20.000' 78 | }, { 79 | startDate: new Date(2000,1,1,10,0,0), 80 | endDate: new Date(2000,1,1,10,0,0,250), 81 | expected: '00:00:00.250' 82 | }, { 83 | startDate: new Date(2000,1,1,10,0,0), 84 | endDate: new Date(2000,1,1,15,20,10,453), 85 | expected: '05:20:10.453' 86 | } 87 | ].forEach(data => { 88 | assert.equal( 89 | data.expected, 90 | tasks.timeSpanToString(data.startDate, data.endDate) 91 | ); 92 | }); 93 | 94 | }); 95 | 96 | 97 | it.optional('angleBetweenClockHands should returns the angle bettween clock hands for specified Greenwich datetime', function () { 98 | [ 99 | { 100 | date: Date.UTC(2016,3,5, 0, 0), 101 | expected: 0 102 | }, { 103 | date: Date.UTC(2016,3,5, 3, 0), 104 | expected: Math.PI/2 105 | }, { 106 | date: Date.UTC(2016,3,5,15, 0), 107 | expected: Math.PI/2 108 | }, { 109 | date: Date.UTC(2016,3,5, 6, 0), 110 | expected: Math.PI 111 | }, { 112 | date: Date.UTC(2016,3,5,18, 0), 113 | expected: Math.PI 114 | }, { 115 | date: Date.UTC(2016,3,5, 9, 0), 116 | expected: Math.PI/2 117 | }, { 118 | date: Date.UTC(2016,3,5,21, 0), 119 | expected: Math.PI/2 120 | }, { 121 | date: Date.UTC(2016,3,5,14,20), 122 | expected: 0.8726646259971648 123 | }, { 124 | date: Date.UTC(2016,3,5,23,55), 125 | expected: 0.4799655442984406 126 | } 127 | ].forEach(data => { 128 | assert.equal( 129 | tasks.angleBetweenClockHands(new Date(data.date)), 130 | data.expected, 131 | `Incorrect result for angleBetweenClockHands(${new Date(data.date).toUTCString()}):` 132 | ); 133 | }); 134 | }); 135 | 136 | }); 137 | -------------------------------------------------------------------------------- /test/05-regex-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/05-regex-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('05-regex-tasks', function() { 8 | 9 | it.optional('getRegexForGuid should match the valid GUID', function () { 10 | var result = tasks.getRegexForGuid(); 11 | 12 | [ 13 | '{3F2504E0-4F89-41D3-9A0C-0305E82C3301}', 14 | '{21EC2020-3AEA-4069-A2DD-08002B30309D}', 15 | '{0c74f13f-fa83-4c48-9b33-68921dd72463}' 16 | ].forEach((str) => { 17 | assert( 18 | result.test(str), 19 | `regex does not match '${str}'` 20 | ); 21 | }); 22 | 23 | [ 24 | '{D44EF4F4-280B47E5-91C7-261222A59621}', 25 | '{D1A5279D-B27D-4CD4-A05E-EFDH53D08E8D}', 26 | '{5EDEB36C-9006-467A8D04-AFB6F62CD7D2}', 27 | '677E2553DD4D43B09DA77414DB1EB8EA', 28 | '0c74f13f-fa83-4c48-9b33-68921dd72463', 29 | 'The roof, the roof, the roof is on fire' 30 | ].forEach((str) => { 31 | assert( 32 | result.test(str) == false, 33 | `regex matches '${str}'` 34 | ); 35 | }); 36 | 37 | }); 38 | 39 | 40 | it.optional('getRegexForPitSpot should be implemeted according to task', function () { 41 | var result = tasks.getRegexForPitSpot(); 42 | 43 | [ 'pit', 'spot', 'spate', 'slap two', 'respite' ].forEach((str) => { 44 | assert( 45 | result.test(str), 46 | `regex does not match '${str}'` 47 | ); 48 | }); 49 | 50 | [ ' pt', 'Pot', 'peat', 'part' ].forEach((str) => { 51 | assert( 52 | result.test(str) == false, 53 | `regex matches '${str}'` 54 | ); 55 | }); 56 | 57 | assert( 58 | result.source.length < 13, 59 | `regexp length should be < 13, actual ${result.source.length} ` 60 | ); 61 | }); 62 | 63 | 64 | it.optional('getRegexForIPv4 should match the valid IPv4', function () { 65 | var result = tasks.getRegexForIPv4(); 66 | 67 | [ 68 | '0.0.0.0', 69 | '127.0.0.1', 70 | '10.10.1.1', 71 | '46.61.155.237', 72 | '010.234.015.001' 73 | ].forEach((str) => { 74 | assert( 75 | result.test(str), 76 | `regex does not match '${str}'` 77 | ); 78 | }); 79 | 80 | [ 81 | '300.0.0.0', 82 | '127.0.0.-1', 83 | '23.24.25.26.27', 84 | 'Set dns to 8.8.8.8' 85 | ].forEach((str) => { 86 | assert( 87 | result.test(str) == false, 88 | `regex matches '${str}'` 89 | ); 90 | }); 91 | }); 92 | 93 | 94 | it.optional('getRegexForSSN should match the valid SSN', function () { 95 | var result = tasks.getRegexForSSN(); 96 | 97 | [ 98 | '123-45-6789', 99 | '234-56-2349', 100 | '875-43-0298', 101 | '034-01-0008' 102 | ].forEach((str) => { 103 | assert( 104 | result.test(str), 105 | `regex does not match '${str}'` 106 | ); 107 | }); 108 | 109 | [ 110 | '123456789', 111 | '000-56-2349', 112 | '875-00-0298', 113 | '034-01-0000', 114 | '0S4-H1-HACK' 115 | ].forEach((str) => { 116 | assert( 117 | result.test(str) == false, 118 | `regex matches '${str}'` 119 | ); 120 | }); 121 | 122 | }); 123 | 124 | 125 | it.optional('getPasswordValidator should return the password validator', function () { 126 | var result = tasks.getPasswordValidator(6); 127 | 128 | [ 129 | 'password', 130 | 'PASSWORD', 131 | 'pa55word', 132 | 'PASSW0RD', 133 | 'Pa55', 134 | 'Pa__W0rd', 135 | ' PassW0rd ' 136 | ].forEach((str) => { 137 | assert( 138 | !result.test(str), 139 | `Regex matches '${str}'` 140 | ); 141 | }); 142 | 143 | [ 144 | 'PA55word', 145 | 'passW0rd', 146 | 'pa55W0rd', 147 | 'pa55wordPASSW0RD', 148 | 'a1A2b3B4' 149 | ].forEach((str) => { 150 | assert( 151 | result.test(str), 152 | `Regex does not match '${str}'` 153 | ); 154 | }); 155 | 156 | assert( 157 | !'abcdABCD1234'.match(tasks.getPasswordValidator(20)), 158 | 'Password validator do not validate minLength restriction' 159 | ); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/06-conditions-n-loops-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/06-conditions-n-loops-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('06-conditions-n-loops-tasks', function() { 8 | 9 | it.optional('getFizzBuzz should return the output value according specification', () => { 10 | [ 11 | 1, 2, 4, 7, 8, 11, 13, 14, 16, 17, 19, 22, 23, 26, 28, 29, 31, 32, 34, 12 | 37, 38, 41, 43, 44, 47, 49, 52, 53, 56, 58, 59, 61, 62, 64, 67, 68, 71, 13 | 73, 74, 76, 77, 79, 82, 83, 86, 88, 89, 91, 92, 94, 97, 98 14 | ].forEach(num => { 15 | var actual = tasks.getFizzBuzz(num); 16 | assert.equal( 17 | actual, 18 | num, 19 | `getFizzBuzz shoud return ${num} for ${num}, but actually ${actual}` 20 | ) 21 | }); 22 | 23 | [ 24 | 3, 6, 9, 12, 18, 21, 24, 27, 25 | 33, 36, 39, 42, 48, 51, 54, 57, 26 | 63, 66, 69, 72, 78, 81, 84, 87, 27 | 93, 96, 99 28 | ].forEach(num => { 29 | var actual = tasks.getFizzBuzz(num); 30 | assert.equal( 31 | actual, 32 | 'Fizz', 33 | `getFizzBuzz shoud return 'Fizz' for ${num}, but actually ${actual}` 34 | ) 35 | }); 36 | 37 | [ 38 | 5, 10, 20, 25, 35, 40, 50, 55, 65, 70, 80, 85, 95, 100 39 | ].forEach(num => { 40 | var actual = tasks.getFizzBuzz(num); 41 | assert.equal( 42 | actual, 43 | 'Buzz', 44 | `getFizzBuzz shoud return 'Buzz' for ${num}, but actually ${actual}` 45 | ) 46 | }); 47 | 48 | [ 49 | 15, 30, 45, 60, 75, 90 50 | ].forEach(num => { 51 | var actual = tasks.getFizzBuzz(num); 52 | assert.equal( 53 | actual, 54 | 'FizzBuzz', 55 | `getFizzBuzz shoud return 'FizzBuzz' for ${num}, but actually ${actual}` 56 | ) 57 | }); 58 | 59 | }); 60 | 61 | 62 | it.optional('getFactorial should return the functorial of given number', () => { 63 | [ 64 | { n: 1, expected: 1 }, 65 | { n: 5, expected: 120 }, 66 | { n: 10, expected: 3628800 } 67 | ].forEach(data => { 68 | var actual = tasks.getFactorial(data.n); 69 | assert.equal( 70 | actual, 71 | data.expected, 72 | `${data.n}! = ${data.expected}, but actual ${actual}` 73 | ) 74 | }); 75 | }); 76 | 77 | 78 | it.optional('getSumBetweenNumbers should return the sum inside the specified interval', () => { 79 | [ 80 | { n1: 1, n2: 2, expected: 3 }, 81 | { n1: 5, n2: 10, expected: 45 }, 82 | { n1: -1, n2: 1, expected: 0 } 83 | ].forEach(data => { 84 | var actual = tasks.getSumBetweenNumbers(data.n1, data.n2); 85 | assert.equal( 86 | actual, 87 | data.expected, 88 | `Sum of [${data.n1},${data.n2}] = ${data.expected}, but actual ${actual}` 89 | ) 90 | }); 91 | }); 92 | 93 | 94 | it.optional('isTriangle should check if triangle can be built', () => { 95 | [ 96 | { sides: [ 1, 2, 3] , expected: false }, 97 | { sides: [ 3, 4, 5] , expected: true }, 98 | { sides: [ 10, 1, 1] , expected: false }, 99 | { sides: [ 10, 10, 10] , expected: true }, 100 | ].forEach(data => { 101 | [[0,1,2], [0,2,1], [1,2,0], [1,0,2], [2,0,1], [2,1,0]].forEach(idx => { 102 | var actual = tasks.isTriangle( 103 | data.sides[idx[0]], 104 | data.sides[idx[1]], 105 | data.sides[idx[2]] 106 | ); 107 | assert.equal( 108 | actual, 109 | data.expected, 110 | `Triangle from [${data.sides.toString()}]: expected ${data.expected} but actual ${actual}` 111 | ); 112 | }) 113 | }); 114 | }); 115 | 116 | 117 | it.optional('doRectanglesOverlap should return true if rectangles overlap', () => { 118 | [ 119 | { 120 | rect1: { top: 0, left: 0, width: 10, height: 10 }, 121 | rect2: { top: 5, left: 5, width: 10, height: 10 }, 122 | expected: true 123 | },{ 124 | rect1: { top: 10, left: 10, width: 10, height: 10 }, 125 | rect2: { top: 5, left: 5, width: 15, height: 15 }, 126 | expected: true 127 | },{ 128 | rect1: { top: 10, left: 10, width: 50, height: 5 }, 129 | rect2: { top: 5, left: 5, width: 10, height: 50 }, 130 | expected: true 131 | },{ 132 | rect1: { top: 0, left: 0, width: 90, height: 90 }, 133 | rect2: { top: 25, left: 25, width: 10, height: 10 }, 134 | expected: true 135 | },{ 136 | rect1: { top: 5, left: 5, width: 20, height: 20 }, 137 | rect2: { top: 5, left: 5, width: 40, height: 10 }, 138 | expected: true 139 | },{ 140 | rect1: { top: 5, left: 5, width: 20, height: 20 }, 141 | rect2: { top: 30, left: 5, width: 40, height: 10 }, 142 | expected: false 143 | },{ 144 | rect1: { top: 0, left: 0, width: 90, height: 90 }, 145 | rect2: { top: 25, left:100, width: 10, height: 10 }, 146 | expected: false 147 | } 148 | ].forEach(data => { 149 | assert.equal( 150 | tasks.doRectanglesOverlap(data.rect1, data.rect2), 151 | data.expected, 152 | `doRectanglesOverlap(\n ${JSON.stringify(data.rect1)},\n ${JSON.stringify(data.rect2)}\n): expected ${data.expected}` 153 | ); 154 | }); 155 | }); 156 | 157 | 158 | it.optional('isInsideCircle should return true if point lies inside of the specified circle', () => { 159 | [ 160 | { 161 | circle: { center: { x: 0, y: 0 }, radius: 10 }, 162 | point: { x: 0, y: 0 }, 163 | expected: true 164 | },{ 165 | circle: { center: { x: 5, y: 5 }, radius: 6 }, 166 | point: { x: 5, y: 10.99 }, 167 | expected: true 168 | },{ 169 | circle: { center: { x: 0, y: 0 }, radius: 10 }, 170 | point: { x: 0, y: 10 }, 171 | expected: false 172 | },{ 173 | circle: { center: { x: 5, y: 5 }, radius: 6 }, 174 | point: { x: 0, y: 0 }, 175 | expected: false 176 | },{ 177 | circle: { center: { x: 2, y: 2 }, radius: 1 }, 178 | point: { x: 2.8, y: 2.8 }, 179 | expected: false 180 | },{ 181 | circle: { center: { x: 2, y: 2 }, radius: 4 }, 182 | point: { x: -1, y: -1 }, 183 | expected: false 184 | },{ 185 | circle: { center: { x: 2, y: 2 }, radius: 4 }, 186 | point: { x: 2, y: 6.1 }, 187 | expected: false 188 | } 189 | ].forEach(data => { 190 | assert.equal( 191 | tasks.isInsideCircle(data.circle, data.point), 192 | data.expected, 193 | `isInsideCircle(\n ${JSON.stringify(data.circle)},\n ${JSON.stringify(data.point)}\n): expected ${data.expected}` 194 | ); 195 | }); 196 | }); 197 | 198 | 199 | it.optional('findFirstSingleChar should return the first unrepeated char from string', () => { 200 | [ 201 | { str: 'The quick brown fox jumps over the lazy dog', expected: 'T' }, 202 | { str: 'abracadabra', expected: 'c' }, 203 | { str: 'entente', expected: null } 204 | ].forEach(data => { 205 | var actual = tasks.findFirstSingleChar(data.str); 206 | assert.equal( 207 | actual, 208 | data.expected, 209 | `First single char of '${data.str}' = '${data.expected}', but actual '${actual}'` 210 | ) 211 | }); 212 | }); 213 | 214 | 215 | it.optional('getIntervalString should return the string representation of math interval', () => { 216 | [ 217 | { 218 | a: 0, 219 | b: 1, 220 | isStartIncluded: true, 221 | isEndIncluded: true, 222 | expected: '[0, 1]' 223 | },{ 224 | a: 0, 225 | b: 1, 226 | isStartIncluded: true, 227 | isEndIncluded: false, 228 | expected: '[0, 1)' 229 | },{ 230 | a: 0, 231 | b: 1, 232 | isStartIncluded: false, 233 | isEndIncluded: true, 234 | expected: '(0, 1]' 235 | },{ 236 | a: 0, 237 | b: 1, 238 | isStartIncluded: false, 239 | isEndIncluded: false, 240 | expected: '(0, 1)' 241 | },{ 242 | a: 5, 243 | b: 3, 244 | isStartIncluded: true, 245 | isEndIncluded: true, 246 | expected: '[3, 5]' 247 | } 248 | ].forEach(data => { 249 | var actual = tasks.getIntervalString(data.a, data.b, data.isStartIncluded, data.isEndIncluded); 250 | assert.equal( 251 | actual, 252 | data.expected, 253 | `getIntervalString(${data.a}, ${data.b}, ${data.isStartIncluded}, ${data.isEndIncluded}) shoud return '${data.expected}', but actually '${actual}'` 254 | ) 255 | }); 256 | }); 257 | 258 | 259 | it.optional('reverseString should return the specified string in reverse order', () => { 260 | [ 261 | { str: 'The quick brown fox jumps over the lazy dog', expected: 'god yzal eht revo spmuj xof nworb kciuq ehT' }, 262 | { str: 'abracadabra', expected: 'arbadacarba' }, 263 | { str: 'rotator', expected: 'rotator' }, 264 | { str: 'noon', expected : 'noon'} 265 | ].forEach(data => { 266 | var actual = tasks.reverseString(data.str); 267 | assert.equal( 268 | actual, 269 | data.expected, 270 | `Reversed '${data.str}' = '${data.expected}', but actual '${actual}'` 271 | ) 272 | }); 273 | }); 274 | 275 | 276 | it.optional('reverseInteger should return the specified number in reverse order', () => { 277 | [ 278 | { num: 12345, expected: 54321 }, 279 | { num: 1111, expected: 1111 }, 280 | { num: 87354, expected: 45378 }, 281 | { num: 34143, expected :34143 } 282 | ].forEach(data => { 283 | var actual = tasks.reverseInteger(data.num); 284 | assert.equal( 285 | actual, 286 | data.expected, 287 | `Reversed ${data.num} = ${data.expected}, but actual ${actual}` 288 | ) 289 | }); 290 | }); 291 | 292 | 293 | it.optional('isCreditCardNumber should validate CCN', () => { 294 | [ 295 | 79927398713, 296 | 4012888888881881, 297 | 5123456789012346, 298 | 378282246310005, 299 | 371449635398431, 300 | 378734493671000, 301 | 5610591081018250, 302 | 30569309025904, 303 | 38520000023237, 304 | 6011111111111117, 305 | 6011000990139424, 306 | 3530111333300000, 307 | 3566002020360505, 308 | 5555555555554444, 309 | 5105105105105100, 310 | 4111111111111111, 311 | 4012888888881881, 312 | 4222222222222, 313 | 5019717010103742, 314 | 6331101999990016, 315 | 54891243456789010 316 | ].forEach(ccn => { 317 | assert( 318 | tasks.isCreditCardNumber(ccn), 319 | `CCN ${ccn} is valid, but actually not` 320 | ) 321 | }); 322 | 323 | 324 | [ 325 | 4571234567890111, 326 | 5436468789016589, 327 | 4916123456789012, 328 | 371449635398430, 329 | 9112893456789010 330 | ].forEach(ccn => { 331 | assert( 332 | tasks.isCreditCardNumber(ccn) == false, 333 | `CCN ${ccn} is not valid, but actually yes` 334 | ) 335 | }); 336 | 337 | }); 338 | 339 | 340 | it.optional('getDigitalRoot should return the cyclic sum of all digits', () => { 341 | [ 342 | { num: 12345, expected: 6 }, 343 | { num: 23456, expected: 2 }, 344 | { num: 10000, expected: 1 }, 345 | { num: 165536, expected: 8 } 346 | ].forEach(data => { 347 | var actual = tasks.getDigitalRoot(data.num); 348 | assert.equal( 349 | actual, 350 | data.expected, 351 | `GetDigitalRoot(${data.num}) = ${data.expected}, but actual ${actual}` 352 | ) 353 | }); 354 | }); 355 | 356 | 357 | it.optional('isBracketsBalanced should check the balanced brackets', () => { 358 | [ 359 | '[]', '[[][][[]]]', '[[][]]', '', '<>', '{}', '()', '<()>', '{<>}', '[{}]', 360 | '[{(<()[]{}<>>)}]', '{}<>()[]','{<>}{()}[[]](())' 361 | ].forEach(str => { 362 | assert( 363 | tasks.isBracketsBalanced(str), 364 | `'${str}' has balanced brackets, but actually not` 365 | ) 366 | }); 367 | 368 | 369 | [ 370 | '[[]', '][', '[][][][][[]', '{)', '<]','(}', '[{]}','{<}>','{{[(])}}','{}()[]<', 371 | '{','(','[','({}[]<>(((())))','{{[]}}>' 372 | ].forEach(str => { 373 | assert( 374 | tasks.isBracketsBalanced(str) == false, 375 | `'${str}' has unbalanced brackets, but actually yes` 376 | ) 377 | }); 378 | 379 | }); 380 | 381 | 382 | it.optional('toNaryString should return the n-ary string representation of number', () => { 383 | [ 384 | { num: 1024, n: 2, expected: '10000000000' }, 385 | { num: 6561, n: 3, expected: '100000000' }, 386 | { num: 365, n: 2, expected: '101101101' }, 387 | { num: 365, n: 3, expected: '111112' }, 388 | { num: 365, n: 4, expected: '11231' }, 389 | { num: 365, n: 5, expected: '2430' }, 390 | { num: 365, n: 6, expected: '1405' }, 391 | { num: 365, n: 7, expected: '1031' }, 392 | { num: 365, n: 9, expected: '445' }, 393 | { num: 365, n:10, expected: '365' }, 394 | ].forEach(data => { 395 | var actual = tasks.toNaryString(data.num, data.n); 396 | assert.equal( 397 | actual, 398 | data.expected, 399 | `${data.num} with radix ${data.n} = ${data.expected}, but actual ${actual}` 400 | ) 401 | }); 402 | }); 403 | 404 | 405 | it.optional('getCommonDirectoryPath should return the n-ary string representation of number', () => { 406 | [ 407 | { 408 | pathes: ['/web/images/image1.png', '/web/images/image2.png'], 409 | expected: '/web/images/' 410 | },{ 411 | pathes: ['/web/assets/style.css', '/web/scripts/app.js', 'home/setting.conf'], 412 | expected: '' 413 | },{ 414 | pathes: ['/web/assets/style.css', '/.bin/mocha', '/read.me'], 415 | expected: '/' 416 | },{ 417 | pathes: ['/web/favicon.ico', '/web-scripts/dump', '/webalizer/logs'], 418 | expected: '/' 419 | } 420 | ].forEach(data => { 421 | var actual = tasks.getCommonDirectoryPath(data.pathes, data.n); 422 | assert.equal( 423 | actual, 424 | data.expected, 425 | `Common directory path fo [${data.pathes}] = ${data.expected}, but actual ${actual}` 426 | ) 427 | }); 428 | }); 429 | 430 | 431 | it.optional('getMatrixProduct should return the product of two specified matrices', () => { 432 | [ 433 | { 434 | m1: [ 435 | [ 1, 0, 0 ], 436 | [ 0, 1, 0 ], 437 | [ 0, 0, 1 ] 438 | ], 439 | m2: [ 440 | [ 1, 2, 3 ], 441 | [ 4, 5, 6 ], 442 | [ 7, 8, 9 ] 443 | ], 444 | expected: [ 445 | [ 1, 2, 3 ], 446 | [ 4, 5, 6 ], 447 | [ 7, 8, 9 ] 448 | ] 449 | },{ 450 | m1: [ 451 | [ 1, 2, 3] 452 | ], 453 | m2: [ 454 | [ 4 ], 455 | [ 5 ], 456 | [ 6 ] 457 | ], 458 | expected : [[ 32 ]] 459 | } 460 | ].forEach(data => { 461 | var actual = tasks.getMatrixProduct(data.m1, data.m2); 462 | assert.deepEqual( 463 | actual, 464 | data.expected, 465 | `Product of [${data.m1}] x [${data.m2}] = [${data.expected}], but actual ${actual}` 466 | ) 467 | }); 468 | }); 469 | 470 | 471 | it.optional('timespanToHumanString should return the human string representation of datetime period', () => { 472 | [ 473 | { 474 | startDate: '2000-01-01 01:00:00.100', 475 | endDate: '2000-01-01 01:00:00.200', 476 | expected: 'a few seconds ago' 477 | }, { 478 | startDate: '2000-01-01 01:00:00.000', 479 | endDate: '2000-01-01 01:00:30.000', 480 | expected: 'a few seconds ago' 481 | }, { 482 | startDate: '2000-01-01 01:00:00.000', 483 | endDate: '2000-01-01 01:00:45.000', 484 | expected: 'a few seconds ago' 485 | }, { 486 | startDate: '2000-01-01 01:00:00.000', 487 | endDate: '2000-01-01 01:00:45.001', 488 | expected: 'a minute ago' 489 | }, { 490 | startDate: '2000-01-01 01:00:00.000', 491 | endDate: '2000-01-01 01:01:00.000', 492 | expected: 'a minute ago' 493 | }, { 494 | startDate: '2000-01-01 01:00:00.000', 495 | endDate: '2000-01-01 01:01:30.000', 496 | expected: 'a minute ago' 497 | }, { 498 | startDate: '2000-01-01 01:00:00.000', 499 | endDate: '2000-01-01 01:01:30.001', 500 | expected: '2 minutes ago' 501 | }, { 502 | startDate: '2000-01-01 01:00:00.000', 503 | endDate: '2000-01-01 01:05:30.000', 504 | expected: '5 minutes ago' 505 | },{ 506 | startDate: '2000-01-01 01:00:00.000', 507 | endDate: '2000-01-01 01:45:00.000', 508 | expected: '45 minutes ago' 509 | },{ 510 | startDate: '2000-01-01 01:00:00.000', 511 | endDate: '2000-01-01 01:45:00.001', 512 | expected: 'an hour ago' 513 | },{ 514 | startDate: '2000-01-01 01:00:00.000', 515 | endDate: '2000-01-01 02:00:00.000', 516 | expected: 'an hour ago' 517 | },{ 518 | startDate: '2000-01-01 01:00:00.000', 519 | endDate: '2000-01-01 02:30:00.000', 520 | expected: 'an hour ago' 521 | },{ 522 | startDate: '2000-01-01 01:00:00.000', 523 | endDate: '2000-01-01 02:30:00.001', 524 | expected: '2 hours ago' 525 | },{ 526 | startDate: '2000-01-01 01:00:00.000', 527 | endDate: '2000-01-01 05:30:00.000', 528 | expected: '4 hours ago' 529 | },{ 530 | startDate: '2000-01-01 01:00:00.000', 531 | endDate: '2000-01-01 05:30:00.001', 532 | expected: '5 hours ago' 533 | },{ 534 | startDate: '2000-01-01 01:00:00.000', 535 | endDate: '2000-01-01 23:00:00.000', 536 | expected: '22 hours ago' 537 | },{ 538 | startDate: '2000-01-01 01:00:00.000', 539 | endDate: '2000-01-01 23:00:00.001', 540 | expected: 'a day ago' 541 | },{ 542 | startDate: '2000-01-01 01:00:00.000', 543 | endDate: '2000-01-02 01:00:00.000', 544 | expected: 'a day ago' 545 | },{ 546 | startDate: '2000-01-01 00:00:00.000', 547 | endDate: '2000-01-02 12:00:00.000', 548 | expected: 'a day ago' 549 | },{ 550 | startDate: '2000-01-01 00:00:00.000', 551 | endDate: '2000-01-02 12:00:00.001', 552 | expected: '2 days ago' 553 | },{ 554 | startDate: '2000-01-01 00:00:00.000', 555 | endDate: '2000-01-05 12:00:00.000', 556 | expected: '4 days ago' 557 | },{ 558 | startDate: '2000-01-01 00:00:00.000', 559 | endDate: '2000-01-26 00:00:00.000', 560 | expected: '25 days ago' 561 | },{ 562 | startDate: '2000-01-01 00:00:00.000', 563 | endDate: '2000-01-26 00:00:00.001', 564 | expected: 'a month ago' 565 | },{ 566 | startDate: '2000-01-01 00:00:00.000', 567 | endDate: '2000-02-01 00:00:00.000', 568 | expected: 'a month ago' 569 | },{ 570 | startDate: '2000-01-01 00:00:00.000', 571 | endDate: '2000-02-15 00:00:00.000', 572 | expected: 'a month ago' 573 | },{ 574 | startDate: '2000-01-01 00:00:00.000', 575 | endDate: '2000-02-16 00:00:00.000', 576 | expected: '2 months ago' 577 | },{ 578 | startDate: '2000-01-01 00:00:00.000', 579 | endDate: '2000-05-20 00:00:00.000', 580 | expected: '5 months ago' 581 | },{ 582 | startDate: '2000-01-01 00:00:00.000', 583 | endDate: '2000-12-10 00:00:00.000', 584 | expected: '11 months ago' 585 | },{ 586 | startDate: '2000-01-01 00:00:00.000', 587 | endDate: '2000-12-12 00:00:00.000', 588 | expected: 'a year ago' 589 | },{ 590 | startDate: '2000-01-01 00:00:00.000', 591 | endDate: '2001-02-15 00:00:00.001', 592 | expected: 'a year ago' 593 | },{ 594 | startDate: '2000-01-01 00:00:00.000', 595 | endDate: '2001-06-01 00:00:00.001', 596 | expected: 'a year ago' 597 | },{ 598 | startDate: '2000-01-01 00:00:00.000', 599 | endDate: '2015-02-15 00:00:00.001', 600 | expected: '15 years ago' 601 | } 602 | ].forEach(data => { 603 | var actual = tasks.timespanToHumanString(new Date(data.startDate), new Date(data.endDate)); 604 | assert.equal( 605 | actual, 606 | data.expected, 607 | `timespanToHumanString('${data.startDate}', '${data.endDate}') shoud return '${data.expected}', but actually '${actual}'` 608 | ) 609 | }); 610 | }); 611 | 612 | 613 | it.optional('evaluateTicTacToePosition should return the winner if defined', () => { 614 | const X = 'X', O = '0'; 615 | 616 | function positionToSting(position) { 617 | var result = ''; 618 | for(var i=0; i<3; i++) { 619 | result += '-------------\n| '; 620 | for(var j=0; j<3; j++) { 621 | result += (position[i][j] ? position[i][j] : ' ')+' | '; 622 | } 623 | result += '\n'; 624 | } 625 | result+='-------------'; 626 | return result; 627 | } 628 | 629 | [[ 630 | [ X, X, X ], 631 | [ O, O, ], 632 | [ O, , ] 633 | ],[ 634 | [ , O, O ], 635 | [ X, X, X ], 636 | [ O, , O ] 637 | ],[ 638 | [ , , O ], 639 | [ O, , O ], 640 | [ X, X, X ] 641 | ],[ 642 | [ X, , O ], 643 | [ X, , O ], 644 | [ X, O, ] 645 | ],[ 646 | [ O, X, O ], 647 | [ X, X, O ], 648 | [ O, X, ] 649 | ],[ 650 | [ O, O, X ], 651 | [ X, O, X ], 652 | [ O, X, X ] 653 | ],[ 654 | [ X, O, O ], 655 | [ X, X, O ], 656 | [ O, X, X ] 657 | ],[ 658 | [ O, O, X ], 659 | [ X, X, O ], 660 | [ X, , O ] 661 | ] 662 | ].forEach(data => { 663 | var actual = tasks.evaluateTicTacToePosition(data); 664 | assert.equal( 665 | actual, 666 | X, 667 | `Position: \n${positionToSting(data)}\n The winner is X, but actually '${actual}'` 668 | ) 669 | }); 670 | 671 | [[ 672 | [ O, O, O ], 673 | [ , X, X ], 674 | [ X, , ] 675 | ],[ 676 | [ X, X, ], 677 | [ O, O, O ], 678 | [ X, , X ] 679 | ],[ 680 | [ , , ], 681 | [ X, , X ], 682 | [ O, O, O ] 683 | ],[ 684 | [ O, , X ], 685 | [ O, X, X ], 686 | [ O, X, ] 687 | ],[ 688 | [ X, O, X ], 689 | [ X, O, O ], 690 | [ O, O, X ] 691 | ],[ 692 | [ X, X, O ], 693 | [ X, O, O ], 694 | [ , X, O ] 695 | ],[ 696 | [ O, X, X ], 697 | [ X, O, X ], 698 | [ O, X, O ] 699 | ],[ 700 | [ X, X, O ], 701 | [ X, O, X ], 702 | [ O, , X ] 703 | ] 704 | ].forEach(data => { 705 | var actual = tasks.evaluateTicTacToePosition(data); 706 | assert.equal( 707 | actual, 708 | O, 709 | `Position: \n${positionToSting(data)}\n The winner is O, but actually '${actual}'` 710 | ) 711 | }); 712 | 713 | [[ 714 | [ , , ], 715 | [ , , ], 716 | [ , , ] 717 | ],[ 718 | [ X, , ], 719 | [ O, O, ], 720 | [ , , X ] 721 | ],[ 722 | [ X, O, X ], 723 | [ X, O, X ], 724 | [ O, X, O ] 725 | ],[ 726 | [ X, O, X ], 727 | [ O, X, X ], 728 | [ O, X, O ] 729 | ],[ 730 | [ X, O, X ], 731 | [ O, , O ], 732 | [ X, O, X ] 733 | ] 734 | ].forEach(data => { 735 | var actual = tasks.evaluateTicTacToePosition(data); 736 | assert.equal( 737 | actual, 738 | undefined, 739 | `Position: \n${positionToSting(data)}\n The winner is undefined, but actually '${actual}'` 740 | ) 741 | }); 742 | }); 743 | 744 | }); 745 | -------------------------------------------------------------------------------- /test/07-yield-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/07-yield-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('07-yield-tasks', function() { 8 | 9 | it.optional('get99BottlesOfBeer should return the sequence of song lyric lines', () => { 10 | 11 | var expected = [ 12 | '99 bottles of beer on the wall, 99 bottles of beer.', 13 | 'Take one down and pass it around, 98 bottles of beer on the wall.', 14 | '98 bottles of beer on the wall, 98 bottles of beer.', 15 | 'Take one down and pass it around, 97 bottles of beer on the wall.', 16 | '97 bottles of beer on the wall, 97 bottles of beer.', 17 | 'Take one down and pass it around, 96 bottles of beer on the wall.', 18 | '96 bottles of beer on the wall, 96 bottles of beer.', 19 | 'Take one down and pass it around, 95 bottles of beer on the wall.', 20 | '95 bottles of beer on the wall, 95 bottles of beer.', 21 | 'Take one down and pass it around, 94 bottles of beer on the wall.', 22 | '94 bottles of beer on the wall, 94 bottles of beer.', 23 | 'Take one down and pass it around, 93 bottles of beer on the wall.', 24 | '93 bottles of beer on the wall, 93 bottles of beer.', 25 | 'Take one down and pass it around, 92 bottles of beer on the wall.', 26 | '92 bottles of beer on the wall, 92 bottles of beer.', 27 | 'Take one down and pass it around, 91 bottles of beer on the wall.', 28 | '91 bottles of beer on the wall, 91 bottles of beer.', 29 | 'Take one down and pass it around, 90 bottles of beer on the wall.', 30 | '90 bottles of beer on the wall, 90 bottles of beer.', 31 | 'Take one down and pass it around, 89 bottles of beer on the wall.', 32 | '89 bottles of beer on the wall, 89 bottles of beer.', 33 | 'Take one down and pass it around, 88 bottles of beer on the wall.', 34 | '88 bottles of beer on the wall, 88 bottles of beer.', 35 | 'Take one down and pass it around, 87 bottles of beer on the wall.', 36 | '87 bottles of beer on the wall, 87 bottles of beer.', 37 | 'Take one down and pass it around, 86 bottles of beer on the wall.', 38 | '86 bottles of beer on the wall, 86 bottles of beer.', 39 | 'Take one down and pass it around, 85 bottles of beer on the wall.', 40 | '85 bottles of beer on the wall, 85 bottles of beer.', 41 | 'Take one down and pass it around, 84 bottles of beer on the wall.', 42 | '84 bottles of beer on the wall, 84 bottles of beer.', 43 | 'Take one down and pass it around, 83 bottles of beer on the wall.', 44 | '83 bottles of beer on the wall, 83 bottles of beer.', 45 | 'Take one down and pass it around, 82 bottles of beer on the wall.', 46 | '82 bottles of beer on the wall, 82 bottles of beer.', 47 | 'Take one down and pass it around, 81 bottles of beer on the wall.', 48 | '81 bottles of beer on the wall, 81 bottles of beer.', 49 | 'Take one down and pass it around, 80 bottles of beer on the wall.', 50 | '80 bottles of beer on the wall, 80 bottles of beer.', 51 | 'Take one down and pass it around, 79 bottles of beer on the wall.', 52 | '79 bottles of beer on the wall, 79 bottles of beer.', 53 | 'Take one down and pass it around, 78 bottles of beer on the wall.', 54 | '78 bottles of beer on the wall, 78 bottles of beer.', 55 | 'Take one down and pass it around, 77 bottles of beer on the wall.', 56 | '77 bottles of beer on the wall, 77 bottles of beer.', 57 | 'Take one down and pass it around, 76 bottles of beer on the wall.', 58 | '76 bottles of beer on the wall, 76 bottles of beer.', 59 | 'Take one down and pass it around, 75 bottles of beer on the wall.', 60 | '75 bottles of beer on the wall, 75 bottles of beer.', 61 | 'Take one down and pass it around, 74 bottles of beer on the wall.', 62 | '74 bottles of beer on the wall, 74 bottles of beer.', 63 | 'Take one down and pass it around, 73 bottles of beer on the wall.', 64 | '73 bottles of beer on the wall, 73 bottles of beer.', 65 | 'Take one down and pass it around, 72 bottles of beer on the wall.', 66 | '72 bottles of beer on the wall, 72 bottles of beer.', 67 | 'Take one down and pass it around, 71 bottles of beer on the wall.', 68 | '71 bottles of beer on the wall, 71 bottles of beer.', 69 | 'Take one down and pass it around, 70 bottles of beer on the wall.', 70 | '70 bottles of beer on the wall, 70 bottles of beer.', 71 | 'Take one down and pass it around, 69 bottles of beer on the wall.', 72 | '69 bottles of beer on the wall, 69 bottles of beer.', 73 | 'Take one down and pass it around, 68 bottles of beer on the wall.', 74 | '68 bottles of beer on the wall, 68 bottles of beer.', 75 | 'Take one down and pass it around, 67 bottles of beer on the wall.', 76 | '67 bottles of beer on the wall, 67 bottles of beer.', 77 | 'Take one down and pass it around, 66 bottles of beer on the wall.', 78 | '66 bottles of beer on the wall, 66 bottles of beer.', 79 | 'Take one down and pass it around, 65 bottles of beer on the wall.', 80 | '65 bottles of beer on the wall, 65 bottles of beer.', 81 | 'Take one down and pass it around, 64 bottles of beer on the wall.', 82 | '64 bottles of beer on the wall, 64 bottles of beer.', 83 | 'Take one down and pass it around, 63 bottles of beer on the wall.', 84 | '63 bottles of beer on the wall, 63 bottles of beer.', 85 | 'Take one down and pass it around, 62 bottles of beer on the wall.', 86 | '62 bottles of beer on the wall, 62 bottles of beer.', 87 | 'Take one down and pass it around, 61 bottles of beer on the wall.', 88 | '61 bottles of beer on the wall, 61 bottles of beer.', 89 | 'Take one down and pass it around, 60 bottles of beer on the wall.', 90 | '60 bottles of beer on the wall, 60 bottles of beer.', 91 | 'Take one down and pass it around, 59 bottles of beer on the wall.', 92 | '59 bottles of beer on the wall, 59 bottles of beer.', 93 | 'Take one down and pass it around, 58 bottles of beer on the wall.', 94 | '58 bottles of beer on the wall, 58 bottles of beer.', 95 | 'Take one down and pass it around, 57 bottles of beer on the wall.', 96 | '57 bottles of beer on the wall, 57 bottles of beer.', 97 | 'Take one down and pass it around, 56 bottles of beer on the wall.', 98 | '56 bottles of beer on the wall, 56 bottles of beer.', 99 | 'Take one down and pass it around, 55 bottles of beer on the wall.', 100 | '55 bottles of beer on the wall, 55 bottles of beer.', 101 | 'Take one down and pass it around, 54 bottles of beer on the wall.', 102 | '54 bottles of beer on the wall, 54 bottles of beer.', 103 | 'Take one down and pass it around, 53 bottles of beer on the wall.', 104 | '53 bottles of beer on the wall, 53 bottles of beer.', 105 | 'Take one down and pass it around, 52 bottles of beer on the wall.', 106 | '52 bottles of beer on the wall, 52 bottles of beer.', 107 | 'Take one down and pass it around, 51 bottles of beer on the wall.', 108 | '51 bottles of beer on the wall, 51 bottles of beer.', 109 | 'Take one down and pass it around, 50 bottles of beer on the wall.', 110 | '50 bottles of beer on the wall, 50 bottles of beer.', 111 | 'Take one down and pass it around, 49 bottles of beer on the wall.', 112 | '49 bottles of beer on the wall, 49 bottles of beer.', 113 | 'Take one down and pass it around, 48 bottles of beer on the wall.', 114 | '48 bottles of beer on the wall, 48 bottles of beer.', 115 | 'Take one down and pass it around, 47 bottles of beer on the wall.', 116 | '47 bottles of beer on the wall, 47 bottles of beer.', 117 | 'Take one down and pass it around, 46 bottles of beer on the wall.', 118 | '46 bottles of beer on the wall, 46 bottles of beer.', 119 | 'Take one down and pass it around, 45 bottles of beer on the wall.', 120 | '45 bottles of beer on the wall, 45 bottles of beer.', 121 | 'Take one down and pass it around, 44 bottles of beer on the wall.', 122 | '44 bottles of beer on the wall, 44 bottles of beer.', 123 | 'Take one down and pass it around, 43 bottles of beer on the wall.', 124 | '43 bottles of beer on the wall, 43 bottles of beer.', 125 | 'Take one down and pass it around, 42 bottles of beer on the wall.', 126 | '42 bottles of beer on the wall, 42 bottles of beer.', 127 | 'Take one down and pass it around, 41 bottles of beer on the wall.', 128 | '41 bottles of beer on the wall, 41 bottles of beer.', 129 | 'Take one down and pass it around, 40 bottles of beer on the wall.', 130 | '40 bottles of beer on the wall, 40 bottles of beer.', 131 | 'Take one down and pass it around, 39 bottles of beer on the wall.', 132 | '39 bottles of beer on the wall, 39 bottles of beer.', 133 | 'Take one down and pass it around, 38 bottles of beer on the wall.', 134 | '38 bottles of beer on the wall, 38 bottles of beer.', 135 | 'Take one down and pass it around, 37 bottles of beer on the wall.', 136 | '37 bottles of beer on the wall, 37 bottles of beer.', 137 | 'Take one down and pass it around, 36 bottles of beer on the wall.', 138 | '36 bottles of beer on the wall, 36 bottles of beer.', 139 | 'Take one down and pass it around, 35 bottles of beer on the wall.', 140 | '35 bottles of beer on the wall, 35 bottles of beer.', 141 | 'Take one down and pass it around, 34 bottles of beer on the wall.', 142 | '34 bottles of beer on the wall, 34 bottles of beer.', 143 | 'Take one down and pass it around, 33 bottles of beer on the wall.', 144 | '33 bottles of beer on the wall, 33 bottles of beer.', 145 | 'Take one down and pass it around, 32 bottles of beer on the wall.', 146 | '32 bottles of beer on the wall, 32 bottles of beer.', 147 | 'Take one down and pass it around, 31 bottles of beer on the wall.', 148 | '31 bottles of beer on the wall, 31 bottles of beer.', 149 | 'Take one down and pass it around, 30 bottles of beer on the wall.', 150 | '30 bottles of beer on the wall, 30 bottles of beer.', 151 | 'Take one down and pass it around, 29 bottles of beer on the wall.', 152 | '29 bottles of beer on the wall, 29 bottles of beer.', 153 | 'Take one down and pass it around, 28 bottles of beer on the wall.', 154 | '28 bottles of beer on the wall, 28 bottles of beer.', 155 | 'Take one down and pass it around, 27 bottles of beer on the wall.', 156 | '27 bottles of beer on the wall, 27 bottles of beer.', 157 | 'Take one down and pass it around, 26 bottles of beer on the wall.', 158 | '26 bottles of beer on the wall, 26 bottles of beer.', 159 | 'Take one down and pass it around, 25 bottles of beer on the wall.', 160 | '25 bottles of beer on the wall, 25 bottles of beer.', 161 | 'Take one down and pass it around, 24 bottles of beer on the wall.', 162 | '24 bottles of beer on the wall, 24 bottles of beer.', 163 | 'Take one down and pass it around, 23 bottles of beer on the wall.', 164 | '23 bottles of beer on the wall, 23 bottles of beer.', 165 | 'Take one down and pass it around, 22 bottles of beer on the wall.', 166 | '22 bottles of beer on the wall, 22 bottles of beer.', 167 | 'Take one down and pass it around, 21 bottles of beer on the wall.', 168 | '21 bottles of beer on the wall, 21 bottles of beer.', 169 | 'Take one down and pass it around, 20 bottles of beer on the wall.', 170 | '20 bottles of beer on the wall, 20 bottles of beer.', 171 | 'Take one down and pass it around, 19 bottles of beer on the wall.', 172 | '19 bottles of beer on the wall, 19 bottles of beer.', 173 | 'Take one down and pass it around, 18 bottles of beer on the wall.', 174 | '18 bottles of beer on the wall, 18 bottles of beer.', 175 | 'Take one down and pass it around, 17 bottles of beer on the wall.', 176 | '17 bottles of beer on the wall, 17 bottles of beer.', 177 | 'Take one down and pass it around, 16 bottles of beer on the wall.', 178 | '16 bottles of beer on the wall, 16 bottles of beer.', 179 | 'Take one down and pass it around, 15 bottles of beer on the wall.', 180 | '15 bottles of beer on the wall, 15 bottles of beer.', 181 | 'Take one down and pass it around, 14 bottles of beer on the wall.', 182 | '14 bottles of beer on the wall, 14 bottles of beer.', 183 | 'Take one down and pass it around, 13 bottles of beer on the wall.', 184 | '13 bottles of beer on the wall, 13 bottles of beer.', 185 | 'Take one down and pass it around, 12 bottles of beer on the wall.', 186 | '12 bottles of beer on the wall, 12 bottles of beer.', 187 | 'Take one down and pass it around, 11 bottles of beer on the wall.', 188 | '11 bottles of beer on the wall, 11 bottles of beer.', 189 | 'Take one down and pass it around, 10 bottles of beer on the wall.', 190 | '10 bottles of beer on the wall, 10 bottles of beer.', 191 | 'Take one down and pass it around, 9 bottles of beer on the wall.', 192 | '9 bottles of beer on the wall, 9 bottles of beer.', 193 | 'Take one down and pass it around, 8 bottles of beer on the wall.', 194 | '8 bottles of beer on the wall, 8 bottles of beer.', 195 | 'Take one down and pass it around, 7 bottles of beer on the wall.', 196 | '7 bottles of beer on the wall, 7 bottles of beer.', 197 | 'Take one down and pass it around, 6 bottles of beer on the wall.', 198 | '6 bottles of beer on the wall, 6 bottles of beer.', 199 | 'Take one down and pass it around, 5 bottles of beer on the wall.', 200 | '5 bottles of beer on the wall, 5 bottles of beer.', 201 | 'Take one down and pass it around, 4 bottles of beer on the wall.', 202 | '4 bottles of beer on the wall, 4 bottles of beer.', 203 | 'Take one down and pass it around, 3 bottles of beer on the wall.', 204 | '3 bottles of beer on the wall, 3 bottles of beer.', 205 | 'Take one down and pass it around, 2 bottles of beer on the wall.', 206 | '2 bottles of beer on the wall, 2 bottles of beer.', 207 | 'Take one down and pass it around, 1 bottle of beer on the wall.', 208 | '1 bottle of beer on the wall, 1 bottle of beer.', 209 | 'Take one down and pass it around, no more bottles of beer on the wall.', 210 | 'No more bottles of beer on the wall, no more bottles of beer.', 211 | 'Go to the store and buy some more, 99 bottles of beer on the wall.' 212 | ]; 213 | 214 | var lineNo = 0; 215 | for(let line of tasks.get99BottlesOfBeer()) { 216 | assert.equal( 217 | line, 218 | expected[lineNo++], 219 | `Text mismatch at line no ${lineNo}: ` 220 | ); 221 | } 222 | 223 | assert.equal( 224 | expected.length, 225 | lineNo, 226 | 'Lines count is incorrect:' 227 | ); 228 | }); 229 | 230 | 231 | it.optional('getFibonacciSequence should return the Fibonacci sequence', () => { 232 | 233 | var expected = [ 234 | 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 235 | 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 236 | 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169 237 | ]; 238 | 239 | var index = 0; 240 | for(let num of tasks.getFibonacciSequence()) { 241 | assert.equal( 242 | num, 243 | expected[index++], 244 | `Sequence mismatch at index no ${index}: ` 245 | ); 246 | if (index>=expected.length) break; 247 | } 248 | if (index { 254 | 255 | /* 256 | * source tree (root = 1): 257 | * 258 | * 1 259 | * / | \ 260 | * 2 6 7 261 | * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } 262 | * 3 4 8 263 | * | 264 | * 5 265 | */ 266 | 267 | var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; 268 | node1.children = [ node2, node6, node7 ]; 269 | node2.children = [ node3, node4 ]; 270 | node4.children = [ node5 ]; 271 | node7.children = [ node8 ]; 272 | var expected = [ node1, node2, node3, node4, node5, node6, node7, node8 ]; 273 | var index = 0; 274 | for(let num of tasks.depthTraversalTree(node1)) { 275 | if (index>=expected.length) assert.fail(index, expected.length,`sequence length should be equal to ${expected.length}`); 276 | assert.equal( 277 | num.n, 278 | expected[index++].n, 279 | `Sequence mismatch at index no ${index}: ` 280 | ); 281 | } 282 | if (index0; i--) { 290 | root = { n : i, children : [ root ] }; 291 | } 292 | return root; 293 | } 294 | 295 | function createWideTree() { 296 | var root = { n: 1, children: [] }; 297 | for(var i=2; i<=MAX_NODE_COUNT; i++) { 298 | root.children.push({ n: i }); 299 | } 300 | return root; 301 | } 302 | 303 | it.optional('depthTraversalTree should process a deep tree', () => { 304 | var root = createDeepTree(); 305 | var index = 1; 306 | for(let node of tasks.depthTraversalTree(root)) { 307 | if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); 308 | assert.equal( 309 | node.n, 310 | index, 311 | `Sequence mismatch at index no ${index}: ` 312 | ); 313 | index++; 314 | } 315 | if (index-1 { 319 | var root = createWideTree(); 320 | var index = 1; 321 | for(let node of tasks.depthTraversalTree(root)) { 322 | if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); 323 | assert.equal( 324 | node.n, 325 | index, 326 | `Sequence mismatch at index no ${index}: ` 327 | ); 328 | index++; 329 | } 330 | if (index-1 { 335 | 336 | /* 337 | * source tree (root = 1): 338 | * 339 | * 1 340 | * / | \ 341 | * 2 3 4 342 | * / \ \ => { 1, 2, 3, 4, 5, 6, 7, 8 } 343 | * 5 6 7 344 | * | 345 | * 8 346 | */ 347 | 348 | var node1 = { n:1 }, node2 = { n:2 }, node3 = { n:3 }, node4 = { n:4 }, node5 = { n:5 }, node6 = { n:6 }, node7 = { n:7 }, node8 = { n:8 }; 349 | node1.children = [ node2, node3, node4 ]; 350 | node2.children = [ node5, node6 ]; 351 | node4.children = [ node7 ]; 352 | node6.children = [ node8 ]; 353 | var expected = [ node1, node2, node3, node4, node5, node6, node7, node8 ]; 354 | var index = 0; 355 | for(let num of tasks.breadthTraversalTree(node1)) { 356 | if (index>=expected.length) assert.fail(null,null,`sequence length should be equal to ${expected.length}`); 357 | assert.equal( 358 | num.n, 359 | expected[index++].n, 360 | `Sequence mismatch at index no ${index}: ` 361 | ); 362 | } 363 | if (index { 368 | var root = createDeepTree(); 369 | var index = 1; 370 | for(let node of tasks.breadthTraversalTree(root)) { 371 | if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); 372 | assert.equal( 373 | node.n, 374 | index, 375 | `Sequence mismatch at index no ${index}: ` 376 | ); 377 | index++; 378 | } 379 | if (index-1 { 383 | var root = createWideTree(); 384 | var index = 1; 385 | for(let node of tasks.breadthTraversalTree(root)) { 386 | if (index > MAX_NODE_COUNT) assert.fail(index, MAX_NODE_COUNT,`sequence length should be equal to ${MAX_NODE_COUNT}`); 387 | assert.equal( 388 | node.n, 389 | index, 390 | `Sequence mismatch at index no ${index}: ` 391 | ); 392 | index++; 393 | } 394 | if (index-1 { 399 | const ITEMS_COUNT = 500; 400 | 401 | var odds = function* () { 402 | for(var i=1; true; i+=2) yield i; 403 | }; 404 | var evens = function* () { 405 | for(var i=2; true; i+=2) yield i; 406 | }; 407 | var expected = 1; 408 | var count = 0; 409 | for(let value of tasks.mergeSortedSequences(odds, evens)) { 410 | assert.equal( 411 | value, 412 | expected++ 413 | ); 414 | count++; 415 | if (count==ITEMS_COUNT) break; 416 | } 417 | assert.equal(count, ITEMS_COUNT); 418 | 419 | var zero = function* () { yield 0; } 420 | expected = 0; 421 | count = 0; 422 | for(let value of tasks.mergeSortedSequences(zero, evens)) { 423 | assert.equal( 424 | value, 425 | expected 426 | ); 427 | expected +=2; 428 | count++; 429 | if (count == ITEMS_COUNT) break; 430 | } 431 | assert.equal(count, ITEMS_COUNT); 432 | 433 | 434 | var minus1 = function* () { yield -1; } 435 | expected = -1; 436 | count = 0; 437 | for(let value of tasks.mergeSortedSequences(odds, minus1)) { 438 | assert.equal( 439 | value, 440 | expected 441 | ); 442 | expected +=2; 443 | count++; 444 | if (count == ITEMS_COUNT) break; 445 | } 446 | assert.equal(count, ITEMS_COUNT); 447 | 448 | }); 449 | }); 450 | -------------------------------------------------------------------------------- /test/08-objects-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/08-objects-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('08-objects-tasks', function() { 8 | 9 | it.optional('Rectangle constructor should return the rectangle object', function () { 10 | var rect = new tasks.Rectangle(10,20); 11 | 12 | assert.equal( 13 | typeof rect, 14 | 'object', 15 | 'Result of Rectangle constructor should be an object' 16 | ); 17 | assert( 18 | rect.hasOwnProperty('width'), 19 | 'Result of Rectangle constructor should be an object with "width" property' 20 | ); 21 | assert.equal( 22 | rect.width, 23 | 10, 24 | 'Result of new Rectangle(10,20) should be an object with "width" property equals to 10' 25 | ); 26 | assert( 27 | rect.hasOwnProperty('height'), 28 | 'Result of new Rectangle(10,20) should be an object with "height" property' 29 | ); 30 | assert.equal( 31 | rect.width, 32 | 10, 33 | 'Result of new Rectangle(10,20) should be an object with "height" property equals to 20' 34 | ); 35 | assert.equal( 36 | typeof rect.getArea, 37 | 'function', 38 | 'Result of Rectangle constructor should be an object with "getArea" method' 39 | ); 40 | assert.equal( 41 | rect.getArea(), 42 | 200, 43 | 'Result of (new Rectangle(10,20)).getArea() should return the correct area of specified rectangle' 44 | ); 45 | assert.equal( 46 | (new tasks.Rectangle(3,8)).getArea(), 47 | 24, 48 | 'Result of (new Rectangle(3,8)).getArea() should return the correct area of specified rectangle' 49 | ); 50 | }); 51 | 52 | 53 | it.optional('getJSON should return the JSON representation of specified object', function () { 54 | [ 55 | { 56 | obj: [ 1, 2, 3], 57 | expected: '[1,2,3]' 58 | },{ 59 | obj: { height: 10, width: 20 }, 60 | expected: '{"height":10,"width":20}' 61 | } 62 | ].forEach(data => { 63 | assert.equal( 64 | tasks.getJSON(data.obj), 65 | data.expected 66 | ); 67 | }); 68 | }); 69 | 70 | 71 | it.optional('fromJSON should return the object of specified type from JSON representation', function () { 72 | var MockType = function(a,b,c) { 73 | this.a = a; 74 | this.b = b; 75 | this.c = c; 76 | }; 77 | 78 | [ 79 | { 80 | proto: tasks.Rectangle.prototype, 81 | json: '{ "width":10, "height":20 }', 82 | expected: new tasks.Rectangle(10, 20) 83 | },{ 84 | proto: MockType.prototype, 85 | json: '{ "a":10, "b":20, "c":30 }', 86 | expected: new MockType(10,20,30) 87 | } 88 | ].forEach(data => { 89 | var actual = tasks.fromJSON(data.proto, data.json); 90 | assert.deepEqual( 91 | actual, 92 | data.expected, 93 | 'fromJson method shoud restore all properties from json' 94 | ); 95 | assert.equal( 96 | actual.__proto__, 97 | data.expected.__proto__, 98 | 'fromJson method shoud restore type from prototype argument' 99 | ); 100 | }); 101 | }); 102 | 103 | 104 | it.optional('cssSelectorBuilder should creates css selector object with stringify() method', function () { 105 | const builder = tasks.cssSelectorBuilder; 106 | 107 | // Test simple selectors 108 | assert.equal( 109 | builder.element('div').stringify(), 110 | 'div' 111 | ); 112 | assert.equal( 113 | builder.id('nav-bar').stringify(), 114 | '#nav-bar' 115 | ); 116 | assert.equal( 117 | builder.class('warning').stringify(), 118 | '.warning' 119 | ); 120 | assert.equal( 121 | builder.attr('href$=".png"').stringify(), 122 | '[href$=".png"]' 123 | ); 124 | assert.equal( 125 | builder.pseudoClass('invalid').stringify(), 126 | ':invalid' 127 | ); 128 | assert.equal( 129 | builder.pseudoElement('first-letter').stringify(), 130 | '::first-letter' 131 | ); 132 | 133 | // Test complex selectors 134 | assert.equal( 135 | builder.element('li').id('main').stringify(), 136 | 'li#main' 137 | ); 138 | assert.equal( 139 | builder.element('div').class('container').stringify(), 140 | 'div.container' 141 | ); 142 | assert.equal( 143 | builder.element('div').class('container').class('clickable').stringify(), 144 | 'div.container.clickable' 145 | ); 146 | assert.equal( 147 | builder.id('main').class('container').class('editable').stringify(), 148 | '#main.container.editable' 149 | ); 150 | assert.equal( 151 | builder.element('li').id('home-menu').class('active').stringify(), 152 | 'li#home-menu.active' 153 | ); 154 | assert.equal( 155 | builder.class('container').class('nav-bar').class('navbar-inverted').stringify(), 156 | '.container.nav-bar.navbar-inverted' 157 | ); 158 | assert.equal( 159 | builder.element('a').attr('href$=".png"').pseudoClass('focus').stringify(), 160 | 'a[href$=".png"]:focus' 161 | ); 162 | assert.equal( 163 | builder.element('p').pseudoClass('first-of-type').pseudoElement('first-letter').stringify(), 164 | 'p:first-of-type::first-letter' 165 | ); 166 | assert.equal( 167 | builder.element('input').pseudoClass('focus').pseudoClass('invalid').stringify(), 168 | 'input:focus:invalid' 169 | ); 170 | 171 | // Test combined selectors 172 | assert.equal( 173 | builder.combine( 174 | builder.element('p').pseudoClass('focus'), 175 | '>', 176 | builder.element('a').attr('href$=".png"') 177 | ).stringify(), 178 | 'p:focus > a[href$=".png"]' 179 | ); 180 | 181 | assert.equal( 182 | builder.combine( 183 | builder.element('p').id('introduction'), 184 | '~', 185 | builder.element('img').attr('href$=".png"') 186 | ).stringify(), 187 | 'p#introduction ~ img[href$=".png"]' 188 | ); 189 | 190 | assert.equal( 191 | builder.combine( 192 | builder.id('charter1').class('touch'), 193 | '+', 194 | builder.element('table') 195 | ).stringify(), 196 | '#charter1.touch + table' 197 | ); 198 | 199 | assert.equal( 200 | builder.combine( 201 | builder.element('ul').class('animable'), 202 | ' ', 203 | builder.element('li').pseudoClass('nth-of-type(1)') 204 | ).stringify(), 205 | 'ul.animable li:nth-of-type(1)' 206 | ); 207 | 208 | assert.equal( 209 | builder.combine( 210 | builder.element('div').id('main').class('container').class('draggable'), 211 | '+', 212 | builder.combine( 213 | builder.element('table').id('data'), 214 | '~', 215 | builder.combine( 216 | builder.element('tr').pseudoClass('nth-of-type(even)'), 217 | ' ', 218 | builder.element('td').pseudoClass('nth-of-type(even)') 219 | ) 220 | ) 221 | ).stringify(), 222 | 'div#main.container.draggable + table#data ~ tr:nth-of-type(even) td:nth-of-type(even)' 223 | ); 224 | 225 | // Test validation 226 | [ 227 | () => builder.element('table').element('div'), 228 | () => builder.id('id1').id('id2'), 229 | () => builder.pseudoElement('after').pseudoElement('before'), 230 | ].forEach(fn => { 231 | assert.throws( 232 | fn, 233 | /Element, id and pseudo-element should not occur more then one time inside the selector/, 234 | 235 | '\nPlease throw an exception "Element, id and pseudo-element should not occur more then one time inside the selector" '+ 236 | 'if element, id or pseudo-element occurs twice or more times' 237 | ); 238 | }); 239 | 240 | [ 241 | () => builder.class('draggable').class('animated'), 242 | () => builder.attr('href').attr('title'), 243 | () => builder.pseudoClass('invalid').pseudoClass('focus'), 244 | ].forEach(fn => { 245 | assert.doesNotThrow( 246 | fn, 247 | /Element, id and pseudo-element should not occur more then one time inside the selector/ 248 | ); 249 | }); 250 | 251 | [ 252 | () => builder.id('id').element('div'), 253 | () => builder.class('main').id('id'), 254 | () => builder.attr('href').class('download-link'), 255 | () => builder.pseudoClass('hover').attr('title'), 256 | () => builder.pseudoElement('after').pseudoClass('valid'), 257 | () => builder.pseudoElement('after').id('id'), 258 | ].forEach(fn => { 259 | assert.throws( 260 | fn, 261 | /Selector parts should be arranged in the following order: element, id, class, attribute, pseudo-class, pseudo-element/, 262 | 263 | '\nPlease throw an exception "Selector parts should be arranged in the following order: element, id, class, attribute, pseudo-class, pseudo-element" '+ 264 | 'if selector parts arranged in an invalid order.' 265 | ); 266 | }); 267 | 268 | }); 269 | 270 | }); 271 | -------------------------------------------------------------------------------- /test/09-functions-n-closures-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/09-functions-n-closures-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('09-functions-n-closures-tasks', function() { 8 | 9 | it.optional('getComposition should return the composition of two functions', () => { 10 | [ 11 | { f: Math.sin, g: Math.asin, arg: 0, result: 0 }, 12 | { f: x=>x+1, g: x=>x+1, arg: 1, result: 3 }, 13 | { f: x=>x*x, g: x=>x+2, arg: 5, result: 49 }, 14 | ].forEach(data => { 15 | var actual = tasks.getComposition(data.f, data.g); 16 | assert( 17 | actual(data.arg)==data.result 18 | ) 19 | }); 20 | }); 21 | 22 | 23 | it.optional('getPowerFunction should return the math power function using the specified exponent', () => { 24 | 25 | var power2 = tasks.getPowerFunction(2); 26 | for(var i=0; i<10; i++) { 27 | assert.equal(power2(i), Math.pow(i,2)); 28 | } 29 | 30 | var power05 = tasks.getPowerFunction(0.5); 31 | for(var i=0; i<10; i++) { 32 | assert.equal(power05(i), Math.pow(i, 0.5)); 33 | } 34 | }); 35 | 36 | 37 | it.optional('getPolynom should return the polynom with specified coefficients', () => { 38 | [ 39 | { 40 | polynom: tasks.getPolynom(2,3,5), 41 | results: [ {x: 0, y: 5}, {x: 2, y: 19}, {x: 3, y: 32} ] 42 | },{ 43 | polynom: tasks.getPolynom(1,-3), 44 | results: [ {x:0, y: -3}, {x:2, y: -1}, {x:5, y:2} ] 45 | },{ 46 | polynom: tasks.getPolynom(8), 47 | results: [ {x:0, y:8}, {x:2, y:8}, {x:5, y:8} ] 48 | } 49 | ].forEach(data => { 50 | data.results.forEach(test => { 51 | assert( 52 | test.y == data.polynom(test.x) 53 | ) 54 | }); 55 | }); 56 | }); 57 | 58 | 59 | it.optional('memoize method should cache the result of function', () => { 60 | var numberOfCalls = 0; 61 | var fn = function() { 62 | numberOfCalls++; 63 | return Math.random(); 64 | } 65 | var memoizer = tasks.memoize(fn); 66 | var expected = memoizer(); 67 | assert.equal(numberOfCalls, 1, 'memoize result should evaluate the specified function at first call'); 68 | for(var i=0; i<10; i++) { 69 | let actual = memoizer(); 70 | assert.equal(actual, expected, 'memoize result should return the cached value at second and next calls'); 71 | assert.equal(numberOfCalls, 1, 'memoize result should not evaluate the specified function at second and next calls'); 72 | } 73 | }); 74 | 75 | 76 | it.optional('retry method should try to evaluate the specified function several times', () => { 77 | var maxAttemps = 3; 78 | var attemps = 0; 79 | var expected = 'expected'; 80 | 81 | var fn = function() { 82 | if (++attemps { 92 | var log = ''; 93 | 94 | var logFunc = (text) => ( log += text + '\n'); 95 | var cosLogger = tasks.logger(Math.cos, logFunc); 96 | 97 | var actual = cosLogger(Math.PI); 98 | 99 | assert.equal(actual, -1, 'logger function should return the original result from specified function'); 100 | assert.equal( 101 | log, 102 | 'cos(3.141592653589793) starts\n' 103 | +'cos(3.141592653589793) ends\n', 104 | 'logger function shoud log the start and end of the specified function'); 105 | }); 106 | 107 | 108 | it.optional('logger method should log start and end of call of the specified function', () => { 109 | var isCalling = false; 110 | var log = ''; 111 | 112 | var fn = function testLogger(param, index) { 113 | assert.equal( 114 | log, 115 | 'testLogger(["expected","test",1],0) starts\n', 116 | 'logger function shoud log the start of specified function before calling' 117 | ); 118 | isCalling = true; 119 | return param[index]; 120 | } 121 | 122 | var logFunc = (text) => ( log += text + '\n'); 123 | var logger = tasks.logger(fn, logFunc); 124 | 125 | var actual = logger(["expected", "test", 1], 0); 126 | 127 | assert.equal(isCalling, true, 'logger function should call the specified function'); 128 | assert.equal(actual, 'expected', 'logger function should return the original result from specified function'); 129 | assert.equal( 130 | log, 131 | 'testLogger(["expected","test",1],0) starts\n' 132 | +'testLogger(["expected","test",1],0) ends\n', 133 | 'logger function shoud log the end of specified function after calling'); 134 | }); 135 | 136 | 137 | it.optional('partialUsingArguments should return the function with partial applied arguments', () => { 138 | const fn = (x1,x2,x3,x4) => x1+x2+x3+x4; 139 | assert.equal( 140 | tasks.partialUsingArguments(fn, 'a')('b','c','d'), 141 | 'abcd', 142 | "partialUsingArguments(fn, 'a')('b','c','d')' should return 'abcd'" 143 | ); 144 | assert.equal( 145 | tasks.partialUsingArguments(fn, 'a','b')('c','d'), 146 | 'abcd', 147 | "partialUsingArguments(fn, 'a','b')('c','d')' should return 'abcd'" 148 | ); 149 | assert.equal( 150 | tasks.partialUsingArguments(fn, 'a','b','c')('d'), 151 | 'abcd', 152 | "partialUsingArguments(fn, 'a','b','c')('d') should return 'abcd'" 153 | ); 154 | assert.equal( 155 | tasks.partialUsingArguments(fn, 'a','b','c','d')(), 156 | 'abcd', 157 | "partialUsingArguments(fn, 'a','b','c','d')()' should return 'abcd'" 158 | ); 159 | }); 160 | 161 | 162 | it.optional('getIdGeneratorFunction should return the id generator function', () => { 163 | 164 | var f0 = tasks.getIdGeneratorFunction(0); 165 | for(var i=0; i<1000; i++) { 166 | assert.equal(f0(), i); 167 | } 168 | 169 | var f10 = tasks.getIdGeneratorFunction(10); 170 | var f20 = tasks.getIdGeneratorFunction(20); 171 | for(var i=0; i<1000; i++) { 172 | assert.equal(f10(), 10+i); 173 | assert.equal(f20(), 20+i); 174 | } 175 | }); 176 | 177 | }); 178 | -------------------------------------------------------------------------------- /test/10-katas-1-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/10-katas-1-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('10-katas-1-tasks', function() { 8 | 9 | it.optional('createCompassPoints should return the 32 compass points', () => { 10 | var expected = [ 11 | { abbreviation : 'N', azimuth : 0.00 }, 12 | { abbreviation : 'NbE', azimuth : 11.25 }, 13 | { abbreviation : 'NNE', azimuth : 22.50 }, 14 | { abbreviation : 'NEbN', azimuth : 33.75 }, 15 | 16 | { abbreviation : 'NE', azimuth : 45.00 }, 17 | { abbreviation : 'NEbE', azimuth : 56.25 }, 18 | { abbreviation : 'ENE', azimuth : 67.50 }, 19 | { abbreviation : 'EbN', azimuth : 78.75 }, 20 | 21 | { abbreviation : 'E', azimuth : 90.00 }, 22 | { abbreviation : 'EbS', azimuth : 101.25 }, 23 | { abbreviation : 'ESE', azimuth : 112.50 }, 24 | { abbreviation : 'SEbE', azimuth : 123.75 }, 25 | 26 | { abbreviation : 'SE', azimuth : 135.00 }, 27 | { abbreviation : 'SEbS', azimuth : 146.25 }, 28 | { abbreviation : 'SSE', azimuth : 157.50 }, 29 | { abbreviation : 'SbE', azimuth : 168.75 }, 30 | 31 | { abbreviation : 'S', azimuth : 180.00 }, 32 | { abbreviation : 'SbW', azimuth : 191.25 }, 33 | { abbreviation : 'SSW', azimuth : 202.50 }, 34 | { abbreviation : 'SWbS', azimuth : 213.75 }, 35 | 36 | { abbreviation : 'SW', azimuth : 225.00 }, 37 | { abbreviation : 'SWbW', azimuth : 236.25 }, 38 | { abbreviation : 'WSW', azimuth : 247.50 }, 39 | { abbreviation : 'WbS', azimuth : 258.75 }, 40 | 41 | { abbreviation : 'W', azimuth : 270.00 }, 42 | { abbreviation : 'WbN', azimuth : 281.25 }, 43 | { abbreviation : 'WNW', azimuth : 292.50 }, 44 | { abbreviation : 'NWbW', azimuth : 303.75 }, 45 | 46 | { abbreviation : 'NW', azimuth : 315.00 }, 47 | { abbreviation : 'NWbN', azimuth : 326.25 }, 48 | { abbreviation : 'NNW', azimuth : 337.50 }, 49 | { abbreviation : 'NbW', azimuth : 348.75 } 50 | 51 | ]; 52 | 53 | assert.deepEqual( 54 | tasks.createCompassPoints(), 55 | expected 56 | ); 57 | 58 | }); 59 | 60 | 61 | it.optional('expandBraces should expand the braces from pattern string', () => { 62 | [ 63 | { 64 | str: '~/{Downloads,Pictures}/*.{jpg,gif,png}', 65 | result : [ 66 | '~/Downloads/*.gif', 67 | '~/Downloads/*.jpg', 68 | '~/Downloads/*.png', 69 | '~/Pictures/*.gif', 70 | '~/Pictures/*.jpg', 71 | '~/Pictures/*.png' 72 | ] 73 | }, { 74 | str: 'It{{em,alic}iz,erat}e{d,}, please.', 75 | result : [ 76 | 'Italicize, please.', 77 | 'Italicized, please.', 78 | 'Itemize, please.', 79 | 'Itemized, please.', 80 | 'Iterate, please.', 81 | 'Iterated, please.' 82 | ] 83 | },{ 84 | str: 'thumbnail.{png,jp{e,}g}', 85 | result : [ 86 | 'thumbnail.jpeg', 87 | 'thumbnail.jpg', 88 | 'thumbnail.png' 89 | ] 90 | },{ 91 | str: 'nothing to do', 92 | result : [ 93 | 'nothing to do' 94 | ] 95 | } 96 | ].forEach(data => { 97 | var actual = Array.from(tasks.expandBraces(data.str)); 98 | actual.sort(); 99 | assert.deepEqual( 100 | actual, 101 | data.result, 102 | `'${data.str}' have not expanded correctly:` 103 | ); 104 | }); 105 | }); 106 | 107 | 108 | it.optional('getZigZagMatrix should create a square matrix with zigzag path', () => { 109 | [ 110 | [ 111 | [0] 112 | ],[ 113 | [ 0, 1 ], 114 | [ 2, 3 ] 115 | ],[ 116 | [ 0, 1, 5 ], 117 | [ 2, 4, 6 ], 118 | [ 3, 7, 8 ] 119 | ],[ 120 | [ 0, 1, 5, 6 ], 121 | [ 2, 4, 7, 12 ], 122 | [ 3, 8, 11, 13 ], 123 | [ 9, 10, 14, 15 ] 124 | ],[ 125 | [ 0, 1, 5, 6, 14 ], 126 | [ 2, 4, 7, 13, 15 ], 127 | [ 3, 8, 12, 16, 21 ], 128 | [ 9, 11, 17, 20, 22 ], 129 | [ 10, 18, 19, 23, 24 ], 130 | ],[ 131 | [ 0, 1, 5, 6, 14, 15 ], 132 | [ 2, 4, 7, 13, 16, 25 ], 133 | [ 3, 8, 12, 17, 24, 26 ], 134 | [ 9, 11, 18, 23, 27, 32 ], 135 | [ 10, 19, 22, 28, 31, 33 ], 136 | [ 20, 21, 29, 30, 34, 35 ], 137 | ],[ 138 | [ 0, 1, 5, 6, 14, 15, 27 ], 139 | [ 2, 4, 7, 13, 16, 26, 28 ], 140 | [ 3, 8, 12, 17, 25, 29, 38 ], 141 | [ 9, 11, 18, 24, 30, 37, 39 ], 142 | [ 10, 19, 23, 31, 36, 40, 45 ], 143 | [ 20, 22, 32, 35, 41, 44, 46 ], 144 | [ 21, 33, 34, 42, 43, 47, 48 ], 145 | ] 146 | ].forEach(data => { 147 | var actual = tasks.getZigZagMatrix(data.length); 148 | assert.deepEqual( 149 | actual, 150 | data, 151 | `Zigzag matrix of ${data.length} size has not been produced correctly:` 152 | ); 153 | }); 154 | }); 155 | 156 | 157 | it.optional('canDominoesMakeRow should answer if specified subset of dominoes can be arranged in a row', () => { 158 | [ 159 | [ 160 | [0,1], [1,1] 161 | ],[ 162 | [1,3], [2,3], [1,4], [2,4], [1,5], [2,5] 163 | ],[ 164 | [1,1], [1,2], [2,3], [2,5], [2,6], [3,6], [5,6], [6,6] 165 | ] 166 | ].forEach(data => { 167 | var actual = tasks.canDominoesMakeRow(data); 168 | assert.equal( 169 | actual, 170 | true, 171 | `[${data.join('],[')}] can be arrangement in a row` 172 | ); 173 | }); 174 | 175 | 176 | [ 177 | [ 178 | [0,1], [2,3] 179 | ],[ 180 | [1,1], [2,2], [1,5], [5,6], [6,3] 181 | ],[ 182 | [0,0], [0,1], [0,2], [0,3], [1,1], [1,2], [1,3], [2,2], [2,3], [3,3] 183 | ] 184 | ].forEach(data => { 185 | var actual = tasks.canDominoesMakeRow(data); 186 | assert.equal( 187 | actual, 188 | false, 189 | `[${data.join('],[')}] can't be arrangement in a row` 190 | ); 191 | }); 192 | 193 | }); 194 | 195 | 196 | it.optional('extractRanges should return string expression of ordered list of integers', () => { 197 | [ 198 | { 199 | nums: [ 0, 1, 2, 3, 4, 5 ], 200 | result: '0-5' 201 | },{ 202 | nums: [ 1, 4, 5 ], 203 | result: '1,4,5' 204 | },{ 205 | nums: [ 0, 1, 2, 5, 7, 8, 9], 206 | result: '0-2,5,7-9' 207 | },{ 208 | nums: [ 1, 2, 4, 5], 209 | result: '1,2,4,5' 210 | },{ 211 | nums: [ 0, 1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 212 | 25, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39 ], 213 | result: '0-2,4,6-8,11,12,14-25,27-33,35-39' 214 | }, 215 | ].forEach(data => { 216 | var actual = tasks.extractRanges(data.nums); 217 | assert.equal( 218 | actual, 219 | data.result, 220 | `[${data.nums}] have not expanded correctly:` 221 | ); 222 | }); 223 | }); 224 | 225 | }); 226 | -------------------------------------------------------------------------------- /test/11-katas-2-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/11-katas-2-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('11-katas-2-tasks', function() { 8 | 9 | it.optional('parseBankAccount should return the bank account number from the specified string', () => { 10 | [ 11 | { 12 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 13 | '| || || || || || || || || |\n'+ 14 | '|_||_||_||_||_||_||_||_||_|\n', 15 | result: 0 16 | },{ 17 | text: ' \n'+ 18 | ' | | | | | | | | |\n'+ 19 | ' | | | | | | | | |\n', 20 | result: 111111111, 21 | 22 | },{ 23 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 24 | ' _| _| _| _| _| _| _| _| _|\n'+ 25 | '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n', 26 | result: 222222222 27 | },{ 28 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 29 | ' _| _| _| _| _| _| _| _| _|\n'+ 30 | ' _| _| _| _| _| _| _| _| _|\n', 31 | result: 333333333 32 | },{ 33 | text: ' \n'+ 34 | '|_||_||_||_||_||_||_||_||_|\n'+ 35 | ' | | | | | | | | |\n', 36 | result: 444444444 37 | },{ 38 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 39 | '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n'+ 40 | ' _| _| _| _| _| _| _| _| _|\n', 41 | result: 555555555 42 | },{ 43 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 44 | '|_ |_ |_ |_ |_ |_ |_ |_ |_ \n'+ 45 | '|_||_||_||_||_||_||_||_||_|\n', 46 | result: 666666666 47 | },{ 48 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 49 | ' | | | | | | | | |\n'+ 50 | ' | | | | | | | | |\n', 51 | result: 777777777 52 | },{ 53 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 54 | '|_||_||_||_||_||_||_||_||_|\n'+ 55 | '|_||_||_||_||_||_||_||_||_|\n', 56 | result: 888888888 57 | },{ 58 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 59 | '|_||_||_||_||_||_||_||_||_|\n'+ 60 | ' _| _| _| _| _| _| _| _| _|\n', 61 | result: 999999999 62 | },{ 63 | text: ' _ _ _ _ _ _ _ \n'+ 64 | ' | _| _||_||_ |_ ||_||_|\n'+ 65 | ' ||_ _| | _||_| ||_| _|\n', 66 | result: 123456789 67 | },{ 68 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 69 | '| | _| _|| ||_ |_ ||_||_|\n'+ 70 | '|_||_ _||_| _||_| ||_| _|\n', 71 | result: 23056789 72 | },{ 73 | text: ' _ _ _ _ _ _ _ _ _ \n'+ 74 | '|_| _| _||_||_ |_ |_||_||_|\n'+ 75 | '|_||_ _||_| _||_| _||_| _|\n', 76 | result: 823856989 77 | } 78 | ].forEach(data => { 79 | assert.equal( 80 | tasks.parseBankAccount(data.text), 81 | data.result, 82 | `${data.text} has not parsed correctly:` 83 | ); 84 | }); 85 | }); 86 | 87 | 88 | it.optional('wrapText should return the sequence of lines from the specified string', () => { 89 | const text = 'The String global object is a constructor for strings, or a sequence of characters.'; 90 | [ 91 | { 92 | cols: 26, 93 | expected: [ 94 | 'The String global object', 95 | 'is a constructor for', 96 | 'strings, or a sequence of', 97 | 'characters.' 98 | ] 99 | },{ 100 | cols: 12, 101 | expected: [ 102 | 'The String', 103 | 'global', 104 | 'object is a', 105 | 'constructor', 106 | 'for strings,', 107 | 'or a', 108 | 'sequence of', 109 | 'characters.' 110 | ] 111 | },{ 112 | cols: Number.MAX_SAFE_INTEGER, 113 | expected: [ text ] 114 | } 115 | ].forEach(data => { 116 | assert.deepEqual( 117 | Array.from(tasks.wrapText(text, data.cols)), 118 | data.expected, 119 | `'${text}' has not wrapped correctly for ${data.cols} columns:` 120 | ); 121 | }); 122 | }); 123 | 124 | 125 | it.optional('getPokerHandRank should return the rank of the specified poker hand', () => { 126 | var rankNames = []; 127 | var PokerRank = tasks.PokerRank; 128 | rankNames[PokerRank.StraightFlush] = 'StraightFlush'; 129 | rankNames[PokerRank.FourOfKind] = 'FourOfKind'; 130 | rankNames[PokerRank.FullHouse] = 'FullHouse'; 131 | rankNames[PokerRank.Flush] = 'Flush'; 132 | rankNames[PokerRank.Straight] = 'Straight'; 133 | rankNames[PokerRank.ThreeOfKind] = 'ThreeOfKind'; 134 | rankNames[PokerRank.TwoPairs] = 'TwoPairs'; 135 | rankNames[PokerRank.OnePair] = 'OnePair'; 136 | rankNames[PokerRank.HighCard] = 'HighCard'; 137 | 138 | [ 139 | { 140 | hand: [ '4♥','5♥','6♥','7♥','8♥' ], 141 | expected: PokerRank.StraightFlush 142 | },{ 143 | hand: [ 'A♣','K♣','Q♣','J♣','10♣' ], 144 | expected: PokerRank.StraightFlush 145 | },{ 146 | hand: [ '10♦','9♦','6♦','7♦','8♦' ], 147 | expected: PokerRank.StraightFlush 148 | },{ 149 | hand: [ 'A♠','4♠','3♠','5♠','2♠' ], 150 | expected: PokerRank.StraightFlush 151 | },{ 152 | hand: [ '4♣','4♦','4♥','4♠','10♥' ], 153 | expected: PokerRank.FourOfKind 154 | },{ 155 | hand: [ '2♣','A♦','A♣','A♠','A♥' ], 156 | expected: PokerRank.FourOfKind 157 | },{ 158 | hand: [ '10♣','10♦','6♦','10♠','10♥' ], 159 | expected: PokerRank.FourOfKind 160 | },{ 161 | hand: [ '4♣','4♦','5♦','5♠','5♥' ], 162 | expected: PokerRank.FullHouse 163 | },{ 164 | hand: [ 'A♣','2♦','A♦','2♠','2♥' ], 165 | expected: PokerRank.FullHouse 166 | },{ 167 | hand: [ '4♣','4♦','5♦','5♠','5♥' ], 168 | expected: PokerRank.FullHouse 169 | },{ 170 | hand: [ '4♣','5♣','6♣','7♣','Q♣' ], 171 | expected: PokerRank.Flush 172 | },{ 173 | hand: [ 'A♦','2♦','3♦','4♦','K♦' ], 174 | expected: PokerRank.Flush 175 | },{ 176 | hand: [ 'A♠','Q♠','J♠','10♠','9♠' ], 177 | expected: PokerRank.Flush 178 | },{ 179 | hand: [ '2♥','4♥','5♥','7♥','A♥' ], 180 | expected: PokerRank.Flush 181 | },{ 182 | hand: [ '2♠','3♥','4♥','5♥','6♥' ], 183 | expected: PokerRank.Straight 184 | },{ 185 | hand: [ 'A♠','K♦','Q♦','J♦','10♦' ], 186 | expected: PokerRank.Straight 187 | },{ 188 | hand: [ '10♥','8♥','9♠','7♥','6♦' ], 189 | expected: PokerRank.Straight 190 | },{ 191 | hand: [ '2♥','4♦','5♥','A♦','3♠' ], 192 | expected: PokerRank.Straight 193 | },{ 194 | hand: [ '2♥','2♠','2♦','7♥','A♥' ], 195 | expected: PokerRank.ThreeOfKind 196 | },{ 197 | hand: [ '2♥','4♥','A♥','A♦','A♠' ], 198 | expected: PokerRank.ThreeOfKind 199 | },{ 200 | hand: [ '10♥','9♥','10♦','J♥','10♠' ], 201 | expected: PokerRank.ThreeOfKind 202 | },{ 203 | hand: [ '2♥','4♦','4♥','A♦','A♠' ], 204 | expected: PokerRank.TwoPairs 205 | },{ 206 | hand: [ '3♥','4♥','A♥','3♦','A♠' ], 207 | expected: PokerRank.TwoPairs 208 | },{ 209 | hand: [ '5♥','6♥','A♥','6♦','5♠' ], 210 | expected: PokerRank.TwoPairs 211 | },{ 212 | hand: [ '2♥','4♦','5♥','A♦','A♠' ], 213 | expected: PokerRank.OnePair 214 | },{ 215 | hand: [ '3♥','4♥','10♥','3♦','A♠' ], 216 | expected: PokerRank.OnePair 217 | },{ 218 | hand: [ '5♥','6♥','7♥','8♦','5♠' ], 219 | expected: PokerRank.OnePair 220 | },{ 221 | hand: [ '3♥','4♥','5♥','7♦','8♥' ], 222 | expected: PokerRank.HighCard 223 | },{ 224 | hand: [ 'A♥','K♥','Q♥','J♦','5♠' ], 225 | expected: PokerRank.HighCard 226 | },{ 227 | hand: [ 'A♥','K♥','Q♥','2♦','3♠' ], 228 | expected: PokerRank.HighCard 229 | } 230 | ].forEach(data => { 231 | var actual = tasks.getPokerHandRank(data.hand); 232 | assert( 233 | actual >= PokerRank.HighCard, 234 | 'Invalid return value. The return value should be >= PokerRank.HighCard' 235 | ); 236 | assert( 237 | actual <= PokerRank.StraightFlush, 238 | 'Invalid return value. The return value should be <= PokerRank.StraightFlush' 239 | ); 240 | assert( 241 | actual == data.expected, 242 | `'${data.hand}' is ranked as ${rankNames[data.expected]}, but actually ${rankNames[actual]} ` 243 | ); 244 | }); 245 | }); 246 | 247 | 248 | it.optional('getFigureRectangles should return the sequence of rectagles parts of the specified figure', () => { 249 | [ 250 | { 251 | figure: '+------------+\n'+ 252 | '| |\n'+ 253 | '| |\n'+ 254 | '| |\n'+ 255 | '+------+-----+\n'+ 256 | '| | |\n'+ 257 | '| | |\n'+ 258 | '+------+-----+\n', 259 | expected: [ 260 | '+------------+\n'+ 261 | '| |\n'+ 262 | '| |\n'+ 263 | '| |\n'+ 264 | '+------------+\n', 265 | 266 | '+------+\n'+ 267 | '| |\n'+ 268 | '| |\n'+ 269 | '+------+\n', 270 | 271 | '+-----+\n'+ 272 | '| |\n'+ 273 | '| |\n'+ 274 | '+-----+\n' 275 | ] 276 | },{ 277 | figure: ' +-----+ \n'+ 278 | ' | | \n'+ 279 | '+--+-----+----+\n'+ 280 | '| |\n'+ 281 | '| |\n'+ 282 | '+-------------+\n', 283 | expected: [ 284 | '+-----+\n'+ 285 | '| |\n'+ 286 | '+-----+\n', 287 | 288 | '+-------------+\n'+ 289 | '| |\n'+ 290 | '| |\n'+ 291 | '+-------------+\n' 292 | ] 293 | },{ 294 | figure: ' +--+ \n'+ 295 | ' | | \n'+ 296 | '+--+--+--+\n'+ 297 | '| | |\n'+ 298 | '+--+--+--+\n'+ 299 | ' | | \n'+ 300 | ' +--+ \n', 301 | expected: [ 302 | '+--+\n'+ 303 | '| |\n'+ 304 | '+--+\n', 305 | 306 | '+--+\n'+ 307 | '| |\n'+ 308 | '+--+\n', 309 | 310 | '+--+\n'+ 311 | '| |\n'+ 312 | '+--+\n', 313 | 314 | '+-----+\n'+ 315 | '| |\n'+ 316 | '+-----+\n' 317 | ] 318 | },{ 319 | figure: '++++\n'+ 320 | '++++\n', 321 | expected: [ 322 | '++\n'+ 323 | '++\n', 324 | 325 | '++\n'+ 326 | '++\n', 327 | 328 | '++\n'+ 329 | '++\n', 330 | ] 331 | } 332 | ].forEach(data => { 333 | var actual = Array.from(tasks.getFigureRectangles(data.figure)).sort(); 334 | var expected = data.expected.sort(); 335 | assert.deepEqual( 336 | actual, 337 | expected, 338 | `Figure \n${data.figure} has the following parts:\n${expected.join(',\n')} but actually :\n${actual.join(',\n')}` 339 | ); 340 | }); 341 | }); 342 | 343 | }); 344 | -------------------------------------------------------------------------------- /test/12-katas-3-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var tasks = require('../task/12-katas-3-tasks'); 5 | it.optional = require('../extensions/it-optional'); 6 | 7 | describe('12-katas-3-tasks', function() { 8 | 9 | it.optional('findStringInSnakingPuzzle shoud return true if word occurrs in the specified puzzle', () => { 10 | var puzzle = [ 11 | 'ANGULAR', 12 | 'REDNCAE', 13 | 'RFIDTCL', 14 | 'AGNEGSA', 15 | 'YTIRTSP', 16 | ]; 17 | var puzzleToString = (p) => p.map(x=>' '+x).join('\n'); 18 | [ 19 | 'ANGULAR', 'REACT', 'UNDEFINED', 'RED', 'STRING', 'CLASS', 'ARRAY' 20 | ].forEach(word => { 21 | assert( 22 | tasks.findStringInSnakingPuzzle(puzzle, word), 23 | `Word "${word}" occurrs in puzzle\n${puzzleToString(puzzle)}` 24 | ); 25 | }); 26 | 27 | [ 28 | 'FUNCTION', 'NULL', 'EMBER', 'HOISTING', 'GIT', 'ARENA' 29 | ].forEach(word => { 30 | assert( 31 | !tasks.findStringInSnakingPuzzle(puzzle, word), 32 | `Word "${word}" does not occurr in puzzle\n${puzzleToString(puzzle)}` 33 | ); 34 | }); 35 | }); 36 | 37 | 38 | it.optional('getPermutations should return all possible string permutations', () => { 39 | [ 40 | { 41 | chars: 'a', 42 | expected: [ 'a' ] 43 | },{ 44 | chars: 'ab', 45 | expected: [ 'ab', 'ba' ] 46 | },{ 47 | chars: 'abc', 48 | expected: [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ] 49 | },{ 50 | chars: 'abcd', 51 | expected: [ 52 | 'abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', 53 | 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', 54 | 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', 55 | 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba' 56 | ] 57 | } 58 | ].forEach(data => { 59 | assert.deepEqual( 60 | Array.from(tasks.getPermutations(data.chars)).sort(), 61 | data.expected, 62 | `Incorrect permutations of "${data.chars}"` 63 | ); 64 | }); 65 | assert.equal( 66 | Array.from(tasks.getPermutations('12345')).length, 67 | 120, 68 | 'Number of 5 chars permutations should be 120.' 69 | ); 70 | }); 71 | 72 | 73 | it.optional('getMostProfitFromStockQuotes should return the max profit from stock trading', () => { 74 | [ 75 | { 76 | quotes: [ 1, 2, 3, 4, 5, 6 ], 77 | expected: 15 78 | },{ 79 | quotes: [ 6, 5, 4, 3, 2, 1 ], 80 | expected: 0 81 | },{ 82 | quotes: [ 1, 6, 5, 10, 8, 7 ], 83 | expected: 18 84 | },{ 85 | quotes: [ 31, 312, 3, 35, 33, 3, 44, 123, 126, 2, 4, 1 ], 86 | expected: 798 87 | },{ 88 | quotes: [ 1, 20, 1, 30, 1, 40, 1, 50, 1, 40, 1, 30, 1, 20, 1 ], 89 | expected: 343 90 | } 91 | ].forEach(data => { 92 | var actual = tasks.getMostProfitFromStockQuotes(data.quotes); 93 | assert.equal( 94 | actual, 95 | data.expected, 96 | `Most profit for [${data.quotes}] quotes is ${data.expected} but actually ${actual}` 97 | ); 98 | }); 99 | }); 100 | 101 | 102 | it.optional('urlShortener should return encoded string shorter than original url', () => { 103 | [ 104 | 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul', 105 | 'https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters', 106 | 'https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_plain_text' 107 | ].forEach(data => { 108 | var urlShortener = new tasks.UrlShortener(); 109 | var actual = urlShortener.encode(data); 110 | assert( 111 | data.length / actual.length > 1.5, 112 | `urlShortener.encode for "${data}" returns "${actual}" that is only ${data.length/actual.length} times less than original url` 113 | ); 114 | }); 115 | }); 116 | 117 | 118 | it.optional('urlShortener should decode shorten link to to the original url', () => { 119 | [ 120 | 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul', 121 | 'https://www.example.com/catalog.html?search=mobile+phones&price=100-200&year=2016#top_links', 122 | ].forEach(data => { 123 | var urlShortener = new tasks.UrlShortener(); 124 | var encoded = urlShortener.encode(data); 125 | var actual = urlShortener.decode(encoded); 126 | assert.equal( 127 | data, 128 | actual, 129 | `urlShortener.encode for "${data}" returns "${encoded}" but decode returns "${actual}"` 130 | ); 131 | }); 132 | }); 133 | 134 | }); 135 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --colors --------------------------------------------------------------------------------