Repository: excellalabs/js-best-practices-workshopper Branch: main Commit: 80e0a9648699 Files: 76 Total size: 110.8 KB Directory structure: gitextract_hn_t18ja/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── bin/ │ └── run ├── exercises/ │ ├── bad_practices/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ ├── problem.ru.md │ │ └── solution/ │ │ └── solution.js │ ├── decompose_balancemanager/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ ├── problem.ru.md │ │ └── solution/ │ │ └── solution.js │ ├── decompose_changehandler/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ ├── problem.ru.md │ │ └── solution/ │ │ └── solution.js │ ├── decompose_productinventory/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ ├── problem.ru.md │ │ └── solution/ │ │ └── solution.js │ ├── get_started/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ └── problem.ru.md │ ├── switch_statement/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ ├── problem.ru.md │ │ └── solution/ │ │ └── solution.js │ ├── tdd/ │ │ ├── exercise.js │ │ ├── problem.md │ │ ├── problem.pt-br.md │ │ ├── problem.ru.md │ │ └── solution/ │ │ └── solution.js │ └── verify_vendingmachine/ │ ├── exercise.js │ ├── problem.md │ ├── problem.pt-br.md │ ├── problem.ru.md │ └── solution/ │ ├── balanceManager.js │ ├── changeHandler.js │ ├── productInventory.js │ └── vendingMachine.js ├── helpers/ │ ├── commandHelpers/ │ │ ├── copyFiles.js │ │ └── quizRunner.js │ ├── customCommands.js │ ├── doSerial.js │ ├── menuHelpers/ │ │ ├── showMenu.js │ │ ├── writeTextMultiline.js │ │ └── yesNoMenu.js │ ├── readSubmission.js │ ├── resolveSubmission.js │ ├── runFileStreamTests.js │ └── runTests.js ├── i18n/ │ ├── en.json │ ├── pt-br.json │ └── ru.json ├── js-best-practices.js ├── npm-shrinkwrap.json ├── package.json └── src/ ├── decomposed/ │ ├── balanceManager.js │ ├── changeHandler.js │ ├── productInventory.js │ └── vendingMachine.js ├── expected/ │ ├── balanceManager.js │ ├── changeHandler.js │ ├── productInventory.js │ └── vendingMachine.js └── initial/ ├── balanceManager.js ├── changeHandler.js ├── productInventory.js └── vendingMachine.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text=auto ================================================ FILE: .gitignore ================================================ .vscode node_modules solutionDir *.log ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Excella Consulting Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # JS Best Practices Workshopper > Learn the best practices of writing clean JavaScript code. screenshot 2017-03-18 14 24 09 For more workshoppers, checkout https://nodeschool.io/ ### Getting Started 1. Make sure you have installed: - [node.js](https://nodejs.org/en/) - A text editor (we recommend [VS Code](https://code.visualstudio.com/) or [Sublime](http://www.sublimetext.com/2)) 2. Run the command `npm install -g js-best-practices`. 3. Make a new folder `mkdir jsbp` and `cd jsbp` 4. Run the command `js-best-practices` to start the workshopper! ### Translations - English - Portugese - Russian ================================================ FILE: bin/run ================================================ #!/usr/bin/env node require('../js-best-practices').execute(process.argv.slice(2)) ================================================ FILE: exercises/bad_practices/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runFileStreamTests = require('../../helpers/runFileStreamTests'); var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); // runs unit tests on submission exercise = runFileStreamTests(exercise, function(fileContents, test){ test('should not contain inconsistent quotes', function(){ var regexSingleQuotes = /\'/; var regexDoubleQuotes = /\"/; if (regexSingleQuotes.test(fileContents)) { expect(regexDoubleQuotes.test(fileContents)).to.equal(false); } else { expect(regexSingleQuotes.test(fileContents)).to.equal(false); } }); test('should not contain comment to explain simple method', function() { var regexComment = /\/\//; expect(regexComment.test(fileContents)).to.equal(false); }); }); exercise = runTests(exercise, function(balanceManager, test){ test('should check for undefined variable as well as null', function(){ expect(balanceManager.isValidAmount()).to.equal(false); }); test('should not have globally scoped variable', function (){ try { balanceManager.canAfford(null); } catch(e) {} expect(function() { balanceManager.canAfford(0.1) }).to.not.throw(Error); }); }); module.exports = exercise; ================================================ FILE: exercises/bad_practices/problem.md ================================================ #Bad Practices The goal of this exercise is to learn to be able to identify bad practices in code. First, take the `Best Practices Quiz` located in the lower menu. Then open `balanceManager.js` and fix all of the mistakes you find there. Both of these exercises draw from the Javascript Best Practices Curriculum which can be found here: https://github.com/excellalabs/the-javascript-curriculum. It might be a good idea to look it over if you have not already. ================================================ FILE: exercises/bad_practices/problem.pt-br.md ================================================ #Más Práticas O objetivo desse exercício é aprender a identificar más práticas no código. Primeiro, faça o 'Quiz de boas práticas' localizado na parte de baixo do menu. Após isso, abra o 'balanceManager.js' e conserte os erros que você encontrar. Ambos os exercícios foram escritos com base no Curriculum de Boas práticas de Javascript, que pode ser encontrado aqui: https://github.com/excellalabs/the-javascript-curriculum. Pode ser uma boa idéia olhar esse material, caso ainda não o tenha feito. ================================================ FILE: exercises/bad_practices/problem.ru.md ================================================ # Плохие практики Цель этого упражнения - научиться находить плохие практики в коде. Для начала пройдите тест 'Лучшие практики' расположенный в меню ниже. Затем откройте `balanceManager.js` и исправьте все ошибки которые вы найдёте. Оба этих упражнения взяты из курса 'Javascript Best Practices Curriculum', который может быть найден здесь: https://github.com/excellalabs/the-javascript-curriculum Может быть хорошей идеей просмотреть его, если вы этого ещё не сделали. ================================================ FILE: exercises/bad_practices/solution/solution.js ================================================ var balance = 0; module.exports = { increaseBalance: function(amount){ balance += amount; }, getBalance: function(){ return balance; }, canAfford: function(amount){ var errorMessage; if(!this.isValidAmount(amount)){ errorMessage = 'Invalid Input'; } if(errorMessage){ throw new Error(errorMessage); } return amount <= balance; }, decreaseBalance: function(amount){ if(!this.canAfford(amount)){ var errorMessage = 'Insufficient balance'; } if(errorMessage){ throw new Error(errorMessage); } balance -= amount; }, isValidAmount: function(amount){ if(!amount){ return false; } else { return true; } } }; ================================================ FILE: exercises/decompose_balancemanager/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); exercise = runTests(exercise, function(changeHandler, test) { test('should have a function called increaseBalance()', function() { expect(typeof changeHandler.increaseBalance).to.equal('function'); }); test('should have a function called getBalance()', function() { expect(typeof changeHandler.getBalance).to.equal('function'); }); test('should have a function called canAfford()', function() { expect(typeof changeHandler.canAfford).to.equal('function'); }); test('should have a function called decreaseBalance()', function() { expect(typeof changeHandler.decreaseBalance).to.equal('function'); }); test('should not have a function called getAmount()', function() { expect(typeof changeHandler.getAmount).to.not.equal('function'); }); test('should not have a function called insertCoin()', function() { expect(typeof changeHandler.insertCoin).to.not.equal('function'); }); test('should not have a function called releaseChange()', function() { expect(typeof changeHandler.releaseChange).to.not.equal('function'); }); test('should not have a function called getProducts()', function() { expect(typeof changeHandler.getProducts).to.not.equal('function'); }); test('should not have a function called getProduct()', function() { expect(typeof changeHandler.getProduct).to.not.equal('function'); }); test('should not have a function called isValidAmount()', function() { expect(typeof changeHandler.isValidAmount).to.not.equal('function'); }); }) module.exports = exercise; ================================================ FILE: exercises/decompose_balancemanager/problem.md ================================================ # Separation of Concerns Part 1 This exercise will display the concept of Separation of Concerns. Our file, `vendingMachine.js`, is too large and contains too many functions. In order to make our code simpler and more maintainable, we will be grouping functions together into their own files. To pass this exercise, take the 4 methods and any variables that relate to balance management and move them to `balanceManager.js`. Then, back in vendingMachine.js, change the method of calling those functions from `this` to our new `balanceManager`. That's it! Move those four methods, make sure they are being called by the `balanceManager`, run the tests, and we will have a functioning balanceManager as part of our Vending Machine. ================================================ FILE: exercises/decompose_balancemanager/problem.pt-br.md ================================================ # Separação de Preocupações (SoC) 1 Esse arquivo irá mostrar o conceito de Separação de Preocupações (ou de conceitos). Nosso arquivo 'vendingMachine.js', é muito grande e contém muitas funções. Para simplificar nosso código e facilitar sua manutenção, vamos agrupar as funções em seus próprios arquivos. Para completar esse exercício, pegue os 4 métodos e quaisquer variáveis que sejam relacionadas ao gerenciamento do caixa e mova para 'balanceManager.js'. Então, volte para vendingMachine.js, mude o método de chamada dessas funções de 'this' para o novo 'balanceManager'. É isso! Mova esses 4 métodos, garanta que eles estão sendo chamados a partir de 'balanceManager', rode os testes, assim teremos funcionando o gerenciador do caixa como parte da nossa máquina de vendas. ================================================ FILE: exercises/decompose_balancemanager/problem.ru.md ================================================ # Разделение ответственности. Часть 1 Это упражнение продемонстрирует принцип Разделения Ответственности. Наш файл `vendingMachine.js`, такой большой и содержит так много функций. Чтобы сделать наш код проще и более поддерживаемым, мы сгруппируем функции в свои файлы. Чтобы выполнить упражнение, возьмите 4 метода и несколько переменных, которые относятся к управлению балансом и переместите их в `balanceManager.js`. Затем вернитесь в `vendingMachine.js` и замените способ вызова этих функций с `this` на наш новый `balanceManager`. Всё верно! Переместите 4 метода, убедитесь что они вызываются с помощью `balanceManager`, запустите тесты, и у нас будет функционирующий balanceManager как часть нашего Торгового Автомата. ================================================ FILE: exercises/decompose_balancemanager/solution/solution.js ================================================ var balance = 0; module.exports = { increaseBalance: function(amount){ balance += amount; }, getBalance: function(){ return balance; }, canAfford: function(amount){ return amount <= balance; }, decreaseBalance: function(amount){ if(!canAfford(amount)){ throw new Error('Insufficient balance'); } balance -= amount; } }; ================================================ FILE: exercises/decompose_changehandler/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); exercise = runTests(exercise, function(changeHandler, test) { test('should have a function called getAmount()', function() { expect(typeof changeHandler.getAmount).to.equal('function'); }); test('should not have a function called insertCoin()', function() { expect(typeof changeHandler.insertCoin).to.not.equal('function'); }); test('should not have a function called releaseChange()', function() { expect(typeof changeHandler.releaseChange).to.not.equal('function'); }); test('should not have a function called getProducts()', function() { expect(typeof changeHandler.getProducts).to.not.equal('function'); }); test('should not have a function called getProduct()', function() { expect(typeof changeHandler.getProduct).to.not.equal('function'); }); test('should not have a function called increaseBalance()', function() { expect(typeof changeHandler.increaseBalance).to.not.equal('function'); }); test('should not have a function called getBalance()', function() { expect(typeof changeHandler.getBalance).to.not.equal('function'); }); test('should not have a function called canAfford()', function() { expect(typeof changeHandler.canAfford).to.not.equal('function'); }); test('should not have a function called decreaseBalance()', function() { expect(typeof changeHandler.decreaseBalance).to.not.equal('function'); }); test('should not have a function called isValidAmount()', function() { expect(typeof changeHandler.isValidAmount).to.not.equal('function'); }); }) module.exports = exercise; ================================================ FILE: exercises/decompose_changehandler/problem.md ================================================ # Separation of Concerns Part 2 This is the second exercise to display the concept of Separation of Concerns. We have already separated out all of the balance related methods, now we will look at the change related ones. Take the method that relates to handling change and move it into `changeHandler.js`. Just like last time, make sure that the new `changeHandler` is now calling the function, run the tests, and you will have successfully integrated a `changeHandler` into your Vending Machine. ================================================ FILE: exercises/decompose_changehandler/problem.pt-br.md ================================================ # Separação de Preocupações (SoC) 2 Esse é o segundo exercício para demonstrar o conceito de Separação de Preocupações (ou Conceitos). Nós já separamos todos os métodos relacionados ao caixa, agora iremos olhar para os métodos relacionados com as trocas. Pegue os métodos relacionados a tratar as trocas e mova para dentro do 'changeHandler.js'. Assim como no exercício anterior, garanta que o novo 'changeHandler' esteja chamando as funções, rode os testes, agora você tem integrado com sucesso um 'changeHandler' para sua máquina de vendas. ================================================ FILE: exercises/decompose_changehandler/problem.ru.md ================================================ # Разделение ответственности. Часть 2 Это второе упражнение, демонстрирующее принцип Разделения Ответственности Мы уже отделили методы относящиеся к управлению балансом, сейчас мы взглянем на те, которые относятся к рассчёту сдачи. Переместите все методы, относящиеся к сдаче в файл `changeHandler.js`. Как и в прошлый раз, удостоверьтесь что новый `changeHandler` вызывает функции, проходит тесты, и вы успешно добавите `changeHandler` в ваш Торговый Автомат. ================================================ FILE: exercises/decompose_changehandler/solution/solution.js ================================================ // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter module.exports = { getAmount: function(coinType) { switch(coinType){ case 'p': return 1; case 'n': return 5; case 'd': return 10; case 'q': return 25; default: throw new Error('Unrecognized coin ' + coinType); } } }; ================================================ FILE: exercises/decompose_productinventory/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); exercise = runTests(exercise, function(changeHandler, test) { test('should not have a function called increaseBalance()', function() { expect(typeof changeHandler.increaseBalance).to.not.equal('function'); }); test('should not have a function called getBalance()', function() { expect(typeof changeHandler.getBalance).to.not.equal('function'); }); test('should not have a function called canAfford()', function() { expect(typeof changeHandler.canAfford).to.not.equal('function'); }); test('should not have a function called decreaseBalance()', function() { expect(typeof changeHandler.decreaseBalance).to.not.equal('function'); }); test('should not have a function called getAmount()', function() { expect(typeof changeHandler.getAmount).to.not.equal('function'); }); test('should not have a function called insertCoin()', function() { expect(typeof changeHandler.insertCoin).to.not.equal('function'); }); test('should not have a function called releaseChange()', function() { expect(typeof changeHandler.releaseChange).to.not.equal('function'); }); test('should have a function called getProducts()', function() { expect(typeof changeHandler.getProducts).to.equal('function'); }); test('should have a function called getProduct()', function() { expect(typeof changeHandler.getProduct).to.equal('function'); }); test('should not have a function called isValidAmount()', function() { expect(typeof changeHandler.isValidAmount).to.not.equal('function'); }); }) module.exports = exercise; ================================================ FILE: exercises/decompose_productinventory/problem.md ================================================ # Separation of Concerns Part 3 This is the third and final exercise to display the concept of Separation of Concerns. We have already separated out all of the balance and change methods, so the only ones left (besides the actual vending machine's) have to do with products. Take the two methods that relate to products and move them into `productInventory.js`. Make sure that the new `productInventory` is now calling the functions, run the tests, and you will have a Vending Machine whose concerns are fully separated. ================================================ FILE: exercises/decompose_productinventory/problem.pt-br.md ================================================ # Separação de Preocupações (SoC) 3 Essa é a terceira e última parte do exercício que ensina o conceito de Separação de Preocupações (ou Conceitos). Nós já separamos todos os métodos relacionados ao caixa e as trocas, falta apenas um (não contando com a máquina de vendas) que será os produtos. Pegue os dois métodos realacionados aos produtos e mova para dentro do 'productInventory.js'. Garanta que o novo 'productInventory' esteja chamando as funções, rode os testes, você terá a máquina de vendas com as preocupações completamente separadas. ================================================ FILE: exercises/decompose_productinventory/problem.ru.md ================================================ # Разделение ответственности. Часть 3 Это третье и последнее упражнение, демонстрирующее принцип разделения ответственности. Мы уже отделили от общей логики методы работы с балансом и сдачей, таким образом у нас остались только (не считая самого Торгового Автомата) продукты. Возьмите два метода, которые относятся к продуктам, и переместите их в `productInventory.js`. Удостоверьтесь что `productInventory` вызывает функции, проходит тесты и вы получите Торговый Автомат, чей функционал полностью разделён. ================================================ FILE: exercises/decompose_productinventory/solution/solution.js ================================================ var products = [ { name: 'Skittles', price: 85, id: 'A1' } ]; module.exports = { getProducts: function() { return products; }, getProduct: function(productId) { var product = products.find(function(p) { return p.id === productId; }); return product; } }; ================================================ FILE: exercises/get_started/exercise.js ================================================ var fs = require('fs'); var path = require('path'); var shop = require('../../js-best-practices'); module.exports = function buildExercise(){ printProblem().then(function(){ return waitForInput('press any key to continue...'); }).then(function(){ shop.execute(['init']); }); return { noprint: true }; } function waitForInput(prompt){ return new Promise(function(res, rej){ console.log(prompt); var alreadyRaw = process.stdin.isRaw; process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.once('data', function(){ process.stdin.pause(); process.stdin.setRawMode(alreadyRaw); res(); }); }); } function printProblem(){ return new Promise(function(res, rej){ var lang = shop.i18n.lang() var problem = fs.readFileSync(path.join(__dirname, 'problem' + (lang === 'en' ? '' : '.' + lang) + '.md')); var stream = shop.createMarkdownStream({ meta: { name: 'get started', number: 0 } }); stream.append(problem, 'md'); stream.pipe(require('workshopper-adventure/lib/mseePipe')()) .pipe(process.stdout) stream.on('end', res); }); } ================================================ FILE: exercises/get_started/problem.md ================================================ # Getting Started The goal of this workshopper is to help you learn how to create a resilient codebase using best practices. The workshopper will guide you through some exercises which will have you make incremental improvements to a bad codebase. Through the exercises, you will learn best practices to help keep your code maintainable, as well as some bad practices to avoid. You will be making changes to a vending machine app. You will be given a file called `vendingMachine.js` as well as a few other empty js files. The next few exercises will have you break down the vending machine into a program that still functions the same but is easier to maintain, test, and update. When you exit this instructions screen, the files necessary for the exercises will be downloaded for you. ================================================ FILE: exercises/get_started/problem.pt-br.md ================================================ # Começando O objetivo desse workshopper é ajudar você a aprender como criar uma base de código resiliente usando boas práticas. O workshopper irá te guiar através de alguns exercícios que irão te auxiliar a realizar melhorias incrementais a uma base de código ruim. Através dos exercícios, você aprenderá boas práticas que te ajudarão a ter um código de fácil manutenção, como também a evitar más práticas. Você estará fazendo mudanças a um aplicativo de uma máquina de vendas. Você receberá um arquivo chamado 'vendingMachine.js' como alguns outros arquivos js vazios. Nos próximos exercícios você irá quebrar em partes a máquina de vendas e transformá-la em um programa que ainda funciona da mesma forma, porém de fácil manutenção, testes, e melhorias. Quando você sair dessa tela de instruções, os arquivos necessários serão baixados para você. ================================================ FILE: exercises/get_started/problem.ru.md ================================================ # Введение Цель этого воркшопа - помочь вам научиться создавать отказоустойчивую кодовую базу, используя лучшие практики. Воркшоп содержит несколько упражнений, в которых вы будете улучшать уже существующую плохую кодовую базу. В этих упражнениях вы изучите лучшие практики, которые помогут сохранять ваш код поддерживаемым, а так же избегать плохих практик. Вы внесёте некоторые изменения в приложение торгового аппарата. У вас будет файл `vendingMachine.js`, а так же несколько пустых .js файлов. В следующих упражнениях вы разобьёте торговый аппарат в приложение, которое функционирует точно так же, но легче поддерживаемое, тестируемое и обновляемое. Когда вы выйдете с экрана инструкций, файлы, относящиеся к упражнениям, будут загружены. ================================================ FILE: exercises/switch_statement/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runFileStreamTests = require('../../helpers/runFileStreamTests'); var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); // runs unit tests on submission exercise = runFileStreamTests(exercise, function(fileContents, test){ test('changeHandler.js should not contain the keyword "switch"', function(){ var regex = /switch/; expect(regex.test(fileContents)).to.equal(false); }); }); exercise = runTests(exercise, function(changeHandler, test) { test('getAmount("p") should return 1', function() { var expected = 1; var actual = changeHandler.getAmount('p'); expect(actual).to.eql(expected); }); test('getAmount("q") should return 25', function() { var expected = 25; var actual = changeHandler.getAmount('q'); expect(actual).to.eql(expected); }); test('getAmount() should throw an error', function() { expect(function(){ changeHandler.getAmount(); }).to.throw('Unrecognized coin undefined'); }) }); module.exports = exercise; ================================================ FILE: exercises/switch_statement/problem.md ================================================ # Getting Rid of Switch Statements Switch statements can be useful in simple cases, but they are often considered "code smells" when used to branch into clauses of complex logic. They also can be somewhat difficult to maintain compared to alternative value-retriever constructs like dictionaries. All in all, it's best to avoid switch statements if at all possible. In this exercise, you should refactor the getAmount() method in changeHandler.js to completely remove the switch method while retaining its functionality. It doesn't matter what alternative you choose, but the method should not use a switch statement in any way. ================================================ FILE: exercises/switch_statement/problem.pt-br.md ================================================ # Se livrando de declarações Switch Declarações Switch podem ser úteis em casos simples, mas normalmente são considerados "code smells*" quando usado com muitas cláusulas e lógica complexa. O Switch também pode ser difícil de dar manutenção comparado com alternativas construídas com value-retriever como dictionaries. Contudo, o melhor é evitar declarações Switch sempre que possível. Nesse exercício, você deverá refatorar o método getAmount() no changeHandler.js e remover completamente a declaração Switch sem alterar a funcionalidade. Não importa qual alternativa você escolher, mas o método não deverá mais usar a desclaração switch de qualquer forma. *code smells - código fedido em tradução literal, se refere a códigos de difícil leitura e manutenção. ================================================ FILE: exercises/switch_statement/problem.ru.md ================================================ # Избавляемся от конструкции switch Конструкция switch может быть полезной в самых простых случаях, но чаще всего считается что "код попахивает", если в ней используется более сложная логика. Также их может быть несколько сложнее поддерживать в сравнении с другими конструкциями ключ - значение, такими как словари. В общем, лучше избегать конструкции switch, если это возможно. В этом упражнении вам предстоит отрефакторить метод getAmount() в `changeHandler.js` так, чтобы окончательно избавиться от конструкции switch, сохраняя функциональность метода. Не важно, как вы это реализуете, но метод, в любом случае, не должен использовать конструкцию switch. ================================================ FILE: exercises/switch_statement/solution/solution.js ================================================ // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter var coins = { 'p': 1, 'n': 5, 'd': 10, 'q': 25 }; module.exports = { getAmount: function(coinType) { if(!coins.hasOwnProperty(coinType)){ throw new Error('Unrecognized coin ' + coinType); } return coins[coinType]; } }; ================================================ FILE: exercises/tdd/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); // runs unit tests on submission exercise = runTests(exercise, function(changeHandler, test){ test('should have function called convertToChange', function(){ expect(typeof changeHandler.convertToChange).to.equal('function'); }); test('convertToChange should return a quarter, nickel, dime, and two pennies as change for 42 cents', function(){ var balance = 42; var expected = ['d', 'n', 'p', 'p', 'q']; var actual = changeHandler.convertToChange(balance).sort(); expect(actual).to.eql(expected); }); test('convertToChange should return an empty array for 0 cents', function(){ var balance = 0; var expected = []; var actual = changeHandler.convertToChange(balance); expect(actual).to.eql(expected); }); test('convertToChange should return three quarters and a penny for 76 cents', function(){ var balance = 76; var expected = ['p', 'q', 'q', 'q']; var actual = changeHandler.convertToChange(balance).sort(); expect(actual).to.eql(expected); }); }); module.exports = exercise; ================================================ FILE: exercises/tdd/problem.md ================================================ # TDD This exercise will test your ability to write code based off of the tests that are provided. Using Test-Driven Development, you write the tests for the functionality that you want to implement first. Then, you write the code for the method so that the tests you initially wrote. The tests in this exercise are provided, but they will drive the design of the function you will implement, convertToChange(). convertToChange() should take in a single integer number in cents (e.g. $0.89 = 89) and return an array of strings representing the coins that would add up to this number. For example, if we input 76 cents, we would expect to return ['p', 'q', 'q', 'q']. Keep in mind that the array of coins should be in the highest order in terms of coin value. For example, if 26 is the parameter, you should return 1 penny and 1 quarter in the array, even though you could technically return several other combinations of coins (e.g. 2 dimes, 1 nickel, 1 penny). Run the tests in this exercise to see what the expected output is. Then implement the convertToChange() function in changeHandler.js to verify that the tests pass. ================================================ FILE: exercises/tdd/problem.pt-br.md ================================================ # Desenvolvimento Dirigido a Testes (TDD) Esse exercício irá testar sua habilidade para escrever código baseados no teste que te será entregue. Usando o Desenvolvimento Dirigido a Testes (TDD), primeiro você escreve o teste para a funcionalidade que você irá implementar. Então, você escreve o código do método cujo qual você inicialmente escreveu o teste. O teste desse exercício te será entregue, mas ele que irá te guiar na implementação da função, convertToChange(). convertToChange() deve aceitar como parametro um numero inteiro que representará os centavos (e.g. R$0,89 = 89) e retornar um array com as moedas que podem ser adicionadas somando esse número. Tenha em mente que o array de moedas deve dar prioridade aos maiores valores. Por exemplo, se o parâmetro for 26, você deve retornar 1 centavo e 25 centavos, mesmo que exista outras combinações de moedas possíveis (e.g. 2 moedas de 10 centavos, 1 moeda de 5 centavos e 1 moeda de 1 centavo). Rode o teste nesse exercício e veja o que ele espera como saída. Então, implemente a função convertToChange() no changeHandler.js para verificar se o teste passa. ================================================ FILE: exercises/tdd/problem.ru.md ================================================ # Разработка через тестирование (Test-Driven Development, TDD) Это упражнение проверит ваше умение писать код, основанный на предоставленных тестах. Используя разработку через тестирование, вы пишете тесты для функциональности, которую хотите реализовать в первую очередь. Затем вы пишете код для метода, тесты для которого вы уже написали. В этом упражнении тесты уже готовы, но они будут проверять функционал метода который вы создадите, convertToChange(). convertToChange() принимает в качестве аргумента одно целое число в центах (так, $0.89 = 89) и возвращает массив монет, которые в сумме дадут это число. Обратите внимание на то, что массив должен содержать монеты наибольшего номинала. Например, если мы передаём 26, вы должны вернуть ['p', 'q'] (1 penny и 1 quarter), даже если технически вы можете вернуть другие комбинации монет (н-р 2 dimes, 1 nickel, 1 penny) Запустите тесты в этом упражнении, чтобы увидеть ожидаемый результат. Затем напишите функцию convertToChange() в changeHandler.js чтобы проверить что тесты пройдены. ================================================ FILE: exercises/tdd/solution/solution.js ================================================ // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter var coins = { 'p': 1, 'n': 5, 'd': 10, 'q': 25 }; var coinsByAmount = ['q', 'd', 'n', 'p']; module.exports = { convertToChange: function(amount) { var change = []; for(var i in coinsByAmount){ var coinType = coinsByAmount[i]; var coinValue = coins[coinType]; while(amount >= coinValue){ change.push(coinType); amount -= coinValue; } } return change; } }; ================================================ FILE: exercises/verify_vendingmachine/exercise.js ================================================ var exercise = require('workshopper-exercise')(); var filecheck = require('workshopper-exercise/filecheck'); var expect = require('chai').expect; var runTests = require('../../helpers/runTests'); // checks that the submission file actually exists exercise = filecheck(exercise); // runs unit tests on submission exercise = runTests(exercise, function(vendingMachine, test, suite, beforeEach) { beforeEach(function(){ vendingMachine.releaseChange(); }) const vendingMachineFunctions = [ 'insertCoin', 'vendProduct', 'getProducts', 'releaseChange' ]; vendingMachineFunctions.forEach(function(functionName){ test('vendingMachine should have a function called ' + functionName, function(){ expect(typeof vendingMachine[functionName]).to.equal('function'); }); }); test('releaseChange should return the balance to zero', function(){ vendingMachine.insertCoin('p'); vendingMachine.insertCoin('q'); expect(vendingMachine.releaseChange()).to.not.eql([]); expect(vendingMachine.releaseChange()).to.eql([]); }); const coinageTestCases = [ { given: [], expected: [] }, { given: ['p','p','p','p','p'], expected: ['n'] }, { given: ['d','d','n'], expected: ['q'] }, { given: ['d','p','d','d'], expected: ['q','n','p'] } ]; coinageTestCases.forEach(function(testCase){ test('vendingMachine should correctly track balance and return correct change', function(){ testCase.given.forEach(function(coin) { vendingMachine.insertCoin(coin); }); expect(vendingMachine.releaseChange()).to.eql(testCase.expected); }); }); }); module.exports = exercise; ================================================ FILE: exercises/verify_vendingmachine/problem.md ================================================ # Verify Vending Machine Now that we've decomposed our initial JavaScript file into three separate, self-contained classes, we should finally test the remaining methods that act as the entry points into these classes' functionality. In practice, you should always delegate specific functionality to its own service and have a controller like ```vendingMachine.js``` orchestrate all the work that these services do. If all has gone well, the only thing you should have to do in this exercise is replace any ```this``` references with references to the classes that call the methods. ================================================ FILE: exercises/verify_vendingmachine/problem.pt-br.md ================================================ # Verifique a sua implementação completa da máquina de vendas Agora que nós decompomos nosso arquivo JavaScript inicial em três arquivos separados, classes auto-contidas, nós finalmente devemos testar os métodos restantes que funcionam como pontos de entrada para as funcionalidades dessas classes. Na prática, você deve sempre delegar funcionalidades específicas para o próprio serviço e ter um controle tipo '''vendingMachine.js''' que orquestrará todo o trabalho que esses serviços fazem. Se tudo correu bem, a única coisa que você deve fazer nesse exercício é trocar qualquer referência a '''this''' para as referencias das classes que chama os métodos. ================================================ FILE: exercises/verify_vendingmachine/problem.ru.md ================================================ # Проверка Торгового Автомата Сейчас, когда мы разделили наш начальный JavaScript файл на три разных, независимых класса, нам следует провести тестирование осталных методов, которые являются точками входа в функционал классов. На практике, вам следует всегда распределять специфичный функционал в свои сервисы и иметь контроллер, такой как ```vendingMachine.js```, управляющий всей работой, которую делают эти сервисы. Если всё прошло хорошо, единственная вещь, которую вам следует сделать в этом упражнении - заменить некоторые ```this``` ссылками на классы, которые вызывают соответствующие методы. ================================================ FILE: exercises/verify_vendingmachine/solution/balanceManager.js ================================================ var balance = 0; module.exports = { increaseBalance: function(amount){ balance += amount; }, getBalance: function(){ return balance; }, canAfford: function(amount){ return amount <= balance; }, decreaseBalance: function(amount){ if(!this.canAfford(amount)){ throw new Error('Insufficient balance'); } balance -= amount; } }; ================================================ FILE: exercises/verify_vendingmachine/solution/changeHandler.js ================================================ // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter var coins = { 'p': 1, 'n': 5, 'd': 10, 'q': 25 }; var coinsByAmount = ['q', 'd', 'n', 'p']; module.exports = { getAmount: function(coinType) { if(!coins.hasOwnProperty(coinType)){ throw new Error('Unrecognized coin ' + coinType); } return coins[coinType]; }, convertToChange: function(amount) { var change = []; for(var i in coinsByAmount){ var coinType = coinsByAmount[i]; var coinValue = coins[coinType]; while(amount >= coinValue){ change.push(coinType); amount -= coinValue; } } return change; } }; ================================================ FILE: exercises/verify_vendingmachine/solution/productInventory.js ================================================ var products = [ { name: 'Skittles', price: 85, id: 'A1' } ]; module.exports = { getProducts: function() { return products; }, getProduct: function(productId) { var product = products.find(function(p) { return p.id === productId; }); return product; } }; ================================================ FILE: exercises/verify_vendingmachine/solution/vendingMachine.js ================================================ var balanceManager = require('./balanceManager'); var changeHandler = require('./changeHandler'); var productInventory = require('./productInventory'); module.exports = { insertCoin: function(coinType){ var value = changeHandler.getAmount(coinType); balanceManager.increaseBalance(value); }, vendProduct: function(productId){ var product = productInventory.getProduct(productId); balanceManager.decreaseBalance(product.price); return product; }, getProducts: function(){ return productInventory.getProducts(); }, releaseChange: function(){ var balance = balanceManager.getBalance(); balanceManager.decreaseBalance(balance); return changeHandler.convertToChange(balance); } }; ================================================ FILE: helpers/commandHelpers/copyFiles.js ================================================ var path = require('path'); var fs = require('fs'); var rimraf = require('rimraf'); var yesNoMenu = require('../menuHelpers/yesNoMenu'); var doSerial = require('../doSerial'); module.exports = function copyFiles(shop, copyFrom, copyTo, relative) { var filesToCopy = fs.readdirSync(copyFrom); return doSerial(filesToCopy, function(file){ var relativePath = path.join(relative || '.', file); var fullCopyPath = path.join(copyFrom, file); var fullDesinationPath = path.join(copyTo, file); var stat = fs.statSync(fullCopyPath); if(stat.isDirectory()) { return tryMakeDirectory(shop, fullDesinationPath).then(function(directoryCreated){ if(directoryCreated){ return copyFiles(shop, fullCopyPath, fullDesinationPath, relativePath); } }); } else { return tryCopyFile(shop, fullCopyPath, fullDesinationPath); } }).then(function(){ console.log('File copy complete'); }); } function tryCopyFile(shop, fullCopyPath, fullDesinationPath){ function doCopy(){ fs.writeFileSync(fullDesinationPath, fs.readFileSync(fullCopyPath)); } if(!fs.existsSync(fullDesinationPath)){ doCopy(); return Promise.resolve(true); } var stat = fs.statSync(fullDesinationPath); return tryDelete(shop, stat, fullDesinationPath).then(function(overwrite) { if(overwrite){ doCopy(); } return overwrite; }); } function tryMakeDirectory(shop, fullDesinationPath){ function makeDirectory(){ fs.mkdirSync(fullDesinationPath); } if(!fs.existsSync(fullDesinationPath)){ makeDirectory(); return Promise.resolve(true); } var stat = fs.statSync(fullDesinationPath); if(stat.isDirectory()){ return Promise.resolve(true); } return tryDelete(shop, stat, fullDesinationPath).then(function(overwrite) { if(overwrite){ makeDirectory(); } return overwrite; }); } function tryDelete(shop, stat, fullPath){ return new Promise(function(res, rej){ var __ = shop.i18n.__; yesNoMenu(__('menu.fileExists', { fileType: getFileType(stat), filePath: fullPath }), __('menu.yes'), __('menu.no')) .then(function(overwrite) { if(!overwrite){ return res(false); } rimraf(fullPath, function(err){ if(err){ return rej(err); } res(true); }); }); }); } function getFileType(stat){ if(stat.isDirectory()){ return 'directory'; } if(stat.isFile()){ return 'file'; } if(stat.isBlockDevice() || stat.isCharacterDevice()){ return 'device'; } if(stat.isSocket()){ return 'socket'; } return 'item'; } ================================================ FILE: helpers/commandHelpers/quizRunner.js ================================================ var showMenu = require('../menuHelpers/showMenu'); var doSerial = require('../doSerial'); const LINE_LENGTH = 73; module.exports = function runQuiz(shop, quizIndex) { var __ = shop.i18n.__; var quiz = __('quizzes.' + quizIndex); return doSerial(quiz, function(question, i){ question.title = question.title || 'Question ' + (i + 1).toString(10); return showQuestion(shop, question, i === quiz.length - 1); }); }; function showQuestion(shop, question, isFinalQuestion) { return new Promise(function(res, rej){ var __ = shop.i18n.__; showMenu(question.title, question.question, question.answers.map(function(answerArray){ var answer = answerArray[0]; var resolutions = answerArray.slice(1); return [answer, function(){ doSerial(resolutions, function(resolution, i){ return showResolution(shop, question, resolution, isFinalQuestion && i === resolutions.length - 1); }).then(res); }]; }), [[__('menu.exit'), process.exit.bind(process, 0)]]); }); } function showResolution(shop, question, resolution, isFinalResolution){ return new Promise(function(res, rej) { var __ = shop.i18n.__; showMenu(question.title, resolution, [[__(isFinalResolution ? 'menu.quizDone' : 'menu.nextQuestion'), res]]); }); } ================================================ FILE: helpers/customCommands.js ================================================ var path = require('path'); var copyFiles = require('./commandHelpers/copyFiles'); var quizRunner = require('./commandHelpers/quizRunner'); module.exports = [{ aliases: ['init'], handler: function(shop) { var userDirectory = process.cwd(); var initialDirectory = path.join(__dirname, '../src/initial'); copyFiles(shop, initialDirectory, userDirectory).then(process.exit.bind(process, 0)) } }, { aliases: ['quiz one'], handler: function(shop) { quizRunner(shop, 0).then(process.exit.bind(process, 0)); } }, { aliases: ['quiz two'], handler: function(shop) { quizRunner(shop, 1).then(process.exit.bind(process, 0)); } }, { aliases: ['select'], order: 1, menu: false, handler: function(shop, args) { var specifier = args.join(' ') var exercise; try { shop.selectExercise(specifier) exercise = shop.loadExercise(specifier); } catch (e) { console.log(e.message) process.exit(1) } if(exercise && exercise.noprint){ return; } shop.execute(['print']); } }]; ================================================ FILE: helpers/doSerial.js ================================================ module.exports = function doSerial(array, toDo){ var i=0; function next() { if(i >= array.length){ return; } return toDo(array[i], i++).then(function() { return next(); }); } return next(); } ================================================ FILE: helpers/menuHelpers/showMenu.js ================================================ var chalk = require('chalk'); var createMenu = require('simple-terminal-menu'); var writeTextMultiline = require('../menuHelpers/writeTextMultiline'); const LINE_LENGTH = 73; module.exports = function showMenu(title, text, menuActions, extraActions){ extraActions = extraActions || []; var menu = createMenu({ width: LINE_LENGTH, x: 2, y: 2 }); menu.writeTitle(title); writeTextMultiline(menu, LINE_LENGTH, text || ''); menu.writeSeparator(); menuActions.forEach(function(action){ menu.add(chalk.bold('» ' + action[0]), action[1]); }); menu.writeSeparator(); extraActions.forEach(function(action){ menu.add(chalk.bold(action[0]), action[1]); }); } ================================================ FILE: helpers/menuHelpers/writeTextMultiline.js ================================================ module.exports = function writeTextMultiline(menu, lineLength, text) { text = text.trim(); if(text.length <= lineLength) { menu.writeLine(text); return; } var breakLineAt = text.lastIndexOf(' ', lineLength); if(breakLineAt === -1){ breakLineAt = lineLength; } var line = text.substring(0, breakLineAt); menu.writeLine(line); writeTextMultiline(menu, lineLength, text.substring(breakLineAt)); } ================================================ FILE: helpers/menuHelpers/yesNoMenu.js ================================================ var showMenu = require('./showMenu'); module.exports = function yesNoMenu(question, yesText, noText, title){ title = title || yesText + ' / ' + noText; return new Promise(function(res, rej){ showMenu(title, question, [[yesText, function() { res(true) }], [noText, function() { res(false) }]]); }); } ================================================ FILE: helpers/readSubmission.js ================================================ const path = require('path'); const fs = require('fs'); function readSubmission(exercise){ var submissionPath = path.resolve(exercise.args[0].toString()); return fs.readFileSync(submissionPath); } module.exports = readSubmission; ================================================ FILE: helpers/resolveSubmission.js ================================================ const path = require('path') function resolveSubmission(exercise){ var submissionPath = path.resolve(exercise.args[0].toString()); return require(submissionPath); } module.exports = resolveSubmission; ================================================ FILE: helpers/runFileStreamTests.js ================================================ const fs = require('fs') , path = require('path') , colors = require('colors/safe') , Mocha = require('mocha') , readSubmission = require('./readSubmission') function createFileStreamTestRunner(definedTestRunner, mochaOptions){ return function processor(callback){ var submission = readSubmission(this) var mochaInstance = new Mocha() mochaInstance.addFile(this.args[0]) mochaInstance.suite.on('post-require', function(context){ definedTestRunner.call(this, submission, context.it) }) mochaInstance.run(callback) } } function runTests(exercise, testRunner, mochaOptions){ exercise.addVerifyProcessor(createFileStreamTestRunner(testRunner, mochaOptions)); exercise.getSolutionFiles = function(callback){ var translated = path.join(this.dir, './solution_' + this.lang) var fallback = path.join(this.dir, './solution') checkPath(translated, function(err, list) { if (list && list.length > 0) return callback(null, list) checkPath(fallback, callback) }); function checkPath(dir, callback) { fs.exists(dir, function (exists) { if (!exists) return callback(null, []); fs.readdir(dir, function (err, list) { if (err) return callback(err) list = list .filter(function (f) { return (/\.js$/).test(f) }) .map(function (f) { return path.join(dir, f)}) callback(null, list) }) }) } } return exercise } module.exports = runTests ================================================ FILE: helpers/runTests.js ================================================ const fs = require('fs') , path = require('path') , colors = require('colors/safe') , Mocha = require('mocha') , resolveSubmission = require('./resolveSubmission') function createTestRunner(definedTestRunner, mochaOptions){ return function processor(callback){ var submission = resolveSubmission(this) var mochaInstance = new Mocha() mochaInstance.addFile(this.args[0]) mochaInstance.suite.on('post-require', function(context){ definedTestRunner.call(this, submission, context.it, context.suite, context.beforeEach) }) mochaInstance.run(function(err){ callback(err); }); } } function runTests(exercise, testRunner, mochaOptions){ exercise.addVerifyProcessor(createTestRunner(testRunner, mochaOptions)); exercise.getSolutionFiles = function(callback){ var translated = path.join(this.dir, './solution_' + this.lang) var fallback = path.join(this.dir, './solution') checkPath(translated, function(err, list) { if (list && list.length > 0) return callback(null, list) checkPath(fallback, callback) }); function checkPath(dir, callback) { fs.exists(dir, function (exists) { if (!exists) return callback(null, []); fs.readdir(dir, function (err, list) { if (err) return callback(err) list = list .filter(function (f) { return (/\.js$/).test(f) }) .map(function (f) { return path.join(dir, f)}) callback(null, list) }) }) } } return exercise } module.exports = runTests ================================================ FILE: i18n/en.json ================================================ { "title": "JAVASCRIPT BEST PRACTICES", "subtitle": "\u001b[23mSelect an exercise and hit \u001b[3mEnter\u001b[23m to begin", "menu": { "credits": "CREDITS", "init": "INITIALIZE WORKING DIRECTORY", "nextQuestion": "Next", "quizDone": "Finish", "quiz one": "BEST PRACTICES QUIZ", "quiz two": "FINAL EXAM", "fileExists": "The {{{fileType}}} at location {{{filePath}}} already exists, do you want to overwrite it?", "yes": "Yes", "no": "No" }, "exercise": { "get started": "Getting Started", "decompose balancemanager": "Separation of Concerns 1", "decompose changehandler": "Separation of Concerns 2", "decompose productinventory": "Separation of Concerns 3", "switch statement": "Getting Rid of Switch Statements", "tdd": "Test Driven Development", "bad practices": "Fix Your Teammates' Mistakes", "verify vendingmachine": "Verify Your Full Vending Machine Implementation" }, "quizzes": [ [{ "question": "Which of the following variable declarations will properly create a string in JavaScript?", "answers": [ ["var message = \"Choice A.\";", "False. Both will properly create a string. Just ensure you choose one way and remain consistent throughout your project."], ["var message = 'Choice B.';", "Both will properly create a string. Just ensure you choose one way and remain consistent throughout your project."], ["Both a) and b) will properly create a string. Just ensure you choose one way and remain consistent throughout your project.", "Correct!"] ] }, { "question": "True or false: The == comparator in JavaScript functions the same way as the === comparator.", "answers": [ ["True", "Incorrect. The === comparator requires both types to be the same to be considered equal. == converts types if necessary before equality comparison."], ["False", "Correct! The === comparator requires both types to be the same to be considered equal. == converts types if necessary before equality comparison."] ] }, { "question": "True or False: null === undefined", "answers": [ ["True", "Incorrect. Null == undefined is true, but the === operator will pick up the difference between the two."], ["False", "Correct! The === operator picks up the difference between the two. Using the == operator instead would make the statement true."] ] }], [{ "question": "True or False: It is good practice to put your scripts within the tag of an HTML document.", "answers": [ ["True", "Incorrect. Browsers render pages more slowly when scripts are in the tag (as opposed to )."], ["False", "Correct! browsers render pages more slowly when scripts are in the tag (as opposed to )."] ] }, { "question": "What does TDD (Test Driven Development) mean?", "answers": [ ["Test-Switch-Code", "Incorrect. The correct answer is 'Red-Green-Refactor.'"], ["Red-Green-Refactor", "Correct!"], ["Blue-Orange-Rewrite", "Incorrect. The correct answer is 'Red-Green-Refactor.'"] ] }, { "question": "True or False: FIRST stands for Fast, Isolated, Repeatable, Self-Verifying, Timely.", "answers": [ ["True", "Correct!"], ["False", "Incorrect."] ] }, { "question": "Is it good practice to use semi-colons in your code?", "answers": [ ["Yes, you absolutely MUST use semi-colons.", "Incorrect. It is up to you; just be consistent."], ["No, you should NEVER use semi-colons.", "Incorrect. It is up to you; just be consistent."], ["It is up to you; just be consistent", "Correct!"] ] }, { "question": "Which of the following is the best way to declare a local variable?", "answers": [ ["myObj = {};", "Incorrect. Use \"var myObj = {}\"."], ["Object myObj;", "Incorrect. Use \"var myObj = {}\"."], ["var myObj = {};", "Correct!"] ] }, { "question": "Select the corresponding SOLID principle: \"Software entities (functions) should be open for extension, but closed for modification.\"", "answers": [ ["Single Responsibility Principle", "Incorrect. Correct answer is Open Closed Principle."], ["Open Closed Principle", "Correct!"], ["Liskov Substitution Principle", "Incorrect. Correct answer is Open Closed Principle."], ["Interface Segregation Principle", "Incorrect. Correct answer is Open Closed Principle."], ["Dependency Inversion Principle", "Incorrect. Correct answer is Open Closed Principle."] ] }, { "question": "Select the corresponding SOLID principle: \"Depend on abstractions, not on concretions.\"", "answers": [ ["Single Responsibility Principle", "Incorrect. Correct answer is Dependency Inversion Principle."], ["Open Closed Principle", "Incorrect. Correct answer is Dependency Inversion Principle."], ["Liskov Substitution Principle", "Incorrect. Correct answer is Dependency Inversion Principle."], ["Interface Segregation Principle", "Incorrect. Correct answer is Dependency Inversion Principle."], ["Dependency Inversion Principle", "Correct!"] ] }, { "question": "Select the corresponding SOLID principle: \"Each function and module should only have one responsibility.\"", "answers": [ ["Single Responsibility Principle", "Correct!"], ["Open Closed Principle", "Incorrect. Correct answer is Single Responsibility Principle."], ["Liskov Substitution Principle", "Incorrect. Correct answer is Single Responsibility Principle."], ["Interface Segregation Principle", "Incorrect. Correct answer is Single Responsibility Principle."], ["Dependency Inversion Principle", "Incorrect. Correct answer is Single Responsibility Principle."] ] }, { "question": "Select the corresponding SOLID principle: \"Clients should not be forced to depend upon interfaces that they do not use.\"", "answers": [ ["Single Responsibility Principle", "Incorrect. Correct answer is Interface Segregation Principle."], ["Open Closed Principle", "Incorrect. Correct answer is Interface Segregation Principle."], ["Liskov Substitution Principle", "Incorrect. Correct answer is Interface Segregation Principle."], ["Interface Segregation Principle", "Correct!"], ["Dependency Inversion Principle", "Incorrect. Correct answer is Interface Segregation Principle."] ] }, { "question": "Select the corresponding SOLID principle: \"Derived classes must be substitutable for their base classe.\"", "answers": [ ["Single Responsibility Principle", "Incorrect. Correct answer is Liskov Substitution Principle."], ["Open Closed Principle", "Incorrect. Correct answer is Liskov Substitution Principle."], ["Liskov Substitution Principle", "Correct!"], ["Interface Segregation Principle", "Incorrect. Correct answer is Liskov Substitution Principle."], ["Dependency Inversion Principle", "Incorrect. Correct answer is Liskov Substitution Principle."] ] }] ] } ================================================ FILE: i18n/pt-br.json ================================================ { "title": "JAVASCRIPT BOAS PRÁTICAS", "subtitle": "\u001b[23mSelecione um exercício e tecle \u001b[3mEnter\u001b[23m para começar", "menu": { "credits": "CRÉDITOS", "init": "INICIALIZAR O DIRETÓRIO DE TRABALHO", "nextQuestion": "Próximo", "quizDone": "Terminar", "quiz one": "QUIZ DE BOAS PRÁTICAS", "quiz two": "EXAME FINAL", "fileExists": "O {{{fileType}}} no local {{{filePath}}} já existe, você deseja sobrescreve-lo?", "yes": "Sim", "no": "Não" }, "exercise": { "get started": "Começando", "decompose balancemanager": "Separação de Preocupações (SoC) 1", "decompose changehandler": "Separação de Preocupações (SoC) 2", "decompose productinventory": "Separação de Preocupações (SoC) 3", "switch statement": "Se livrando de declarações Switch", "tdd": "Desenvolvimento Dirigido a Testes (TDD)", "bad practices": "Conserte os erros de seus colegas", "verify vendingmachine": "Verifique a sua implementação completa da máquina de vendas" }, "quizzes": [ [{ "question": "Qual das seguintes declarações de variáveis irá criar de maneira correta uma string no JavaScript?", "answers": [ ["var message = \"Choice A.\";", "Falso. Ambas irão criar uma string da maneira correta. Apenas garanta que você irá escolher um jeito e não mudará durante o projeto."], ["var message = 'Choice B.';", "Ambas irão criar uma string da maneira correta. Apenas garanta que você irá escolher um jeito e não mudará durante o projeto."], ["Ambas a) e b) irão criar uma string da maneira correta. Apenas garanta que você irá escolher um jeito e não mudará durante o projeto.", "Correto!"] ] }, { "question": "Verdadeiro ou Falso: O operador de comparação == no JavaScript funciona da mesma forma que o comparador === .", "answers": [ ["Verdadeiro", "Incorreto. O comparador === requere que o tipo dos valores comparados sejam o mesmo para serem considerados iguais. == converte o tipo se for necessário para comparação."], ["Falso", "Correto! O comparador === requere que o tipo dos valores comparados sejam o mesmo para serem considerados iguais. == converte o tipo se for necessário para comparação."] ] }, { "question": "Verdadeiro ou Falso: null === undefined", "answers": [ ["Verdadeiro", "Incorreto. null == undefined é verdadeiro, mas o operador === irá retornar que os valores são diferentes."], ["Falso", "Correto! O operador === irá retornar que os valores são diferentes. Usando o operador == fará com que o retorno seja verdadeiro."] ] }], [{ "question": "Verdadeiro ou Falso: É uma boa prática colocar os seus scripts dentro da tag em um documento HTML.", "answers": [ ["Verdadeiro", "Incorreto. Navegadores demoram mais para renderizar páginas quando os scripts estão na tag (ao contrário da tag )."], ["Falso", "Correto! Navegadores demoram mais para renderizar páginas quando os scripts estão na tag (ao contrário da tag )."] ] }, { "question": "Como podemos definir o TDD (Test Driven Development)?", "answers": [ ["Test-Switch-Code", "Incorreto. A resposta correta é 'Red-Green-Refactor.'"], ["Red-Green-Refactor", "Correto!"], ["Blue-Orange-Rewrite", "Incorreto. A resposta correta é 'Red-Green-Refactor.'"] ] }, { "question": "Verdadeiro ou Falso: O Acrônimo FIRST significa Fast, Isolated, Repeatable, Self-Verifying, Timely.", "answers": [ ["Verdadeiro", "Correto!"], ["Falso", "Incorreto."] ] }, { "question": "É uma boa prática utilizar ponto-e-vírgula no seu código?", "answers": [ ["Sim, Você definitivamente DEVE usar ponto-e-vírgula no seu código.", "Incorreto. você escolhe! Apenas seja consistente."], ["Não, Você NUNCA deve utilizar ponto-e-vírgula no seu código.", "Incorreto. você escolhe! Apenas seja consistente."], ["Você escolhe! Apenas seja consistente", "Correto!"] ] }, { "question": "Qual das alternativas é a melhor forma de declarar uma variável local?", "answers": [ ["myObj = {};", "Incorreto. Use \"var myObj = {}\"."], ["Object myObj;", "Incorreto. Use \"var myObj = {}\"."], ["var myObj = {};", "Correto!"] ] }, { "question": "Selecione o princípio do SOLID correspondente: \"Software entities (functions) should be open for extension, but closed for modification.\"", "answers": [ ["Single Responsibility Principle", "Incorreto. A resposta correta é Open Closed Principle."], ["Open Closed Principle", "Correto!"], ["Liskov Substitution Principle", "Incorreto. A resposta correta é Open Closed Principle."], ["Interface Segregation Principle", "Incorreto. A resposta correta é Open Closed Principle."], ["Dependency Inversion Principle", "Incorreto. A resposta correta é Open Closed Principle."] ] }, { "question": "Selecione o princípio do SOLID correspondente: \"Depend on abstractions, not on concretions.\"", "answers": [ ["Single Responsibility Principle", "Incorreto. A resposta correta é Dependency Inversion Principle."], ["Open Closed Principle", "Incorreto. A resposta correta é Dependency Inversion Principle."], ["Liskov Substitution Principle", "Incorreto. A resposta correta é Dependency Inversion Principle."], ["Interface Segregation Principle", "Incorreto. A resposta correta é Dependency Inversion Principle."], ["Dependency Inversion Principle", "Correto!"] ] }, { "question": "Selecione o princípio do SOLID correspondente: \"Each function and module should only have one responsibility.\"", "answers": [ ["Single Responsibility Principle", "Correto!"], ["Open Closed Principle", "Incorreto. A resposta correta é Single Responsibility Principle."], ["Liskov Substitution Principle", "Incorreto. A resposta correta é Single Responsibility Principle."], ["Interface Segregation Principle", "Incorreto. A resposta correta é Single Responsibility Principle."], ["Dependency Inversion Principle", "Incorreto. A resposta correta é Single Responsibility Principle."] ] }, { "question": "Selecione o princípio do SOLID correspondente: \"Clients should not be forced to depend upon interfaces that they do not use.\"", "answers": [ ["Single Responsibility Principle", "Incorreto. A resposta correta é Interface Segregation Principle."], ["Open Closed Principle", "Incorreto. A resposta correta é Interface Segregation Principle."], ["Liskov Substitution Principle", "Incorreto. A resposta correta é Interface Segregation Principle."], ["Interface Segregation Principle", "Correto!"], ["Dependency Inversion Principle", "Incorreto. A resposta correta é Interface Segregation Principle."] ] }, { "question": "Selecione o princípio do SOLID correspondente: \"Derived classes must be substitutable for their base classe.\"", "answers": [ ["Single Responsibility Principle", "Incorreto. A resposta correta é Liskov Substitution Principle."], ["Open Closed Principle", "Incorreto. A resposta correta é Liskov Substitution Principle."], ["Liskov Substitution Principle", "Correto!"], ["Interface Segregation Principle", "Incorreto. A resposta correta é Liskov Substitution Principle."], ["Dependency Inversion Principle", "Incorreto. A resposta correta é Liskov Substitution Principle."] ] }] ] } ================================================ FILE: i18n/ru.json ================================================ { "title": "Лучшие практики JavaScript", "subtitle": "\u001b[23mВыберите упражнение и нажмите \u001b[3mEnter\u001b[23m чтобы начать", "menu": { "credits": "Авторы", "init": "Создать рабочий каталог", "nextQuestion": "Следующий", "quizDone": "Закончить", "quiz one": "Тест 'Лучшие практики'", "quiz two": "Финальный экзамен", "fileExists": "Файл {{{filePath}}} уже существует, перезаписать?", "yes": "Да", "no": "Нет" }, "exercise": { "get started": "Введение", "decompose balancemanager": "Разделение ответственности. Часть 1", "decompose changehandler": "Разделение ответственности. Часть 2", "decompose productinventory": "Разделение ответственности. Часть 3", "switch statement": "Избавляемся от конструкции switch", "tdd": "Разработка через тестирование (Test-Driven Development, TDD)", "bad practices": "Поправьте ошибки своих коллег", "verify vendingmachine": "Проверка Торгового Автомата" }, "quizzes": [ [{ "question": "Который из следующих вариантов создаст строку в JavaScript?", "answers": [ ["var message = \"Choice A.\";", "Неверно. Оба варианта создадут строку в JavaScript. Просто удостоверьтесь что используете один из вариантов во всём своём проекте."], ["var message = 'Choice B.';", "Оба варианта создадут строку в JavaScript. Просто удостоверьтесь что используете один из вариантов во всём своём проекте."], ["Оба, a) и b) создадут строку. Просто удостоверьтесь что используете один из вариантов во всём своём проекте.", "Правильно!"] ] }, { "question": "Верно или нет: Оператор сравнения == в JavaScript работает так же, как и оператор === .", "answers": [ ["Верно", "Не правильно. Оператор === требует совпадения типов. Оператор == до сравнения, если это возможно, приводит к одному типу."], ["Неверно", "Правильно! Оператор === требует совпадения типов. Оператор == до сравнения, если это возможно, приводит к одному типу."] ] }, { "question": "Верно или нет: null === undefined", "answers": [ ["Верно", "Не правильно. Null == undefined вернёт true, а оператор === найдёт между ними разницу и вернёт false."], ["Неверно", "Правильно! Оператор === найдёт между ними разницу и вернёт false. Используя вместо этого оператор == получим true."] ] }], [{ "question": "Верно или нет: Хорошая практика - помещать скрипты в тэг документа HTML.", "answers": [ ["Верно", "Не правильно. Браузеры рендерят страницы медленнее, когда скрипты находятся в теге (в отличие от )."], ["Не верно", "Правильно! Браузеры рендерят страницы медленнее, когда скрипты находятся в теге (в отличие от )."] ] }, { "question": "Что значит TDD (Test Driven Development) mean?", "answers": [ ["Test-Switch-Code", "Не правильно. Правильный ответ 'Red-Green-Refactor.'"], ["Red-Green-Refactor", "Правильно!"], ["Blue-Orange-Rewrite", "Не правильно. Правильный ответ 'Red-Green-Refactor.'"] ] }, { "question": "Верно или нет: FIRST означает Fast, Isolated, Repeatable, Self-Verifying, Timely.", "answers": [ ["Верно", "Правильно!"], ["Не верно", "Не правильно."] ] }, { "question": "Хорошая ли практика - использовать точки с запятыми в коде?", "answers": [ ["Да, вы обязательно ДОЛЖНЫ использовать точки с запятыми.", "Не верно. Всё зависит от вас, просто будьте последовательны."], ["Нет, и НИКОГДА не должны использовать точки с запятыми.", "Не верно. Всё зависит от вас, просто будьте последовательны."], ["Всё зависит от вас, просто будьте последовательны", "Верно!"] ] }, { "question": "Который из приведённых ниже способов лучший для объявления локальной переменной?", "answers": [ ["myObj = {};", "Не верно. Use \"var myObj = {}\"."], ["Object myObj;", "Не верно. Use \"var myObj = {}\"."], ["var myObj = {};", "Верно!"] ] }, { "question": "Выберите принцип, относящийся к SOLID: \"Программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для изменения.\"", "answers": [ ["Принцип единственной ответственности (Single Responsibility Principle)", "Не верно. Правильный ответ Принцип открытости / закрытости (Open Closed Principle)."], ["Принцип открытости / закрытости (Open Closed Principle)", "Верно!"], ["Принцип подстановки Барбары Лисков (Liskov Substitution Principle)", "Не верно. Правильный ответ Принцип открытости / закрытости (Open Closed Principle)."], ["Принцип разделения интерфейса (Interface Segregation Principle)", "Не верно. Правильный ответ Принцип открытости / закрытости (Open Closed Principle)."], ["Принцип инверсии зависимостей (Dependency Inversion Principle)", "Не верно. Правильный ответ Принцип открытости / закрытости (Open Closed Principle)."] ] }, { "question": "Выберите принцип, относящийся к SOLID: \"Модули должны зависеть от абстракций, а не от деталей.\"", "answers": [ ["Принцип единственной ответственности (Single Responsibility Principle)", "Не верно. Правильный ответ Принцип инверсии зависимостей (Dependency Inversion Principle)."], ["Принцип открытости / закрытости (Open Closed Principle)", "Не верно. Правильный ответ прин­цип инверсии зависимостей (Dependency Inversion Principle)."], ["Принцип подстановки Барбары Лисков (Liskov Substitution Principle)", "Не верно. Правильный ответ Принцип инверсии зависимостей (Dependency Inversion Principle)."], ["Принцип разделения интерфейса (Interface Segregation Principle)", "Не верно. Правильный ответ прин­цип инверсии зависимостей (Dependency Inversion Principle)."], ["Принцип инверсии зависимостей (Dependency Inversion Principle)", "Верно!"] ] }, { "question": "Выберите принцип, относящийся к SOLID: \"Каж­дая функция и модуль дол­жны иметь одну обя­зан­ность .\"", "answers": [ ["Принцип единственной ответственности (Single Responsibility Principle)", "Верно!"], ["Принцип открытости / закрытости (Open Closed Principle)", "Не верно. Правильный ответ Принцип единственной ответственности (Single Responsibility Principle)."], ["Принцип подстановки Барбары Лисков (Liskov Substitution Principle)", "Не верно. Правильный ответ Принцип единственной ответственности (Single Responsibility Principle)."], ["Принцип разделения интерфейса (Interface Segregation Principle)", "Не верно. Правильный ответ Принцип единственной ответственности (Single Responsibility Principle)."], ["Принцип инверсии зависимостей (Dependency Inversion Principle)", "Не верно. Правильный ответ Принцип единственной ответственности (Single Responsibility Principle)."] ] }, { "question": "Выберите принцип, относящийся к SOLID: \"Кли­енты не должны зави­сеть от мето­дов, кото­рые они не исполь­зуют.\"", "answers": [ ["Принцип единственной ответственности (Single Responsibility Principle)", "Не верно. Правильный ответ Принцип разделения интерфейса (Interface Segregation Principle)."], ["Принцип открытости / закрытости (Open Closed Principle)", "Не верно. Правильный ответ прин­цип разделения интерфейса (Interface Segregation Principle)."], ["Принцип подстановки Барбары Лисков (Liskov Substitution Principle)", "Не верно. Правильный ответ Принцип разделения интерфейса (Interface Segregation Principle)."], ["Принцип разделения интерфейса (Interface Segregation Principle)", "Верно!"], ["Принцип инверсии зависимостей (Dependency Inversion Principle)", "Не верно. Правильный ответ Принцип разделения интерфейса (Interface Segregation Principle)."] ] }, { "question": "Выберите принцип, относящийся к SOLID: \"Производные классы должны быть замещаемы на базовые классы.\"", "answers": [ ["Принцип единственной ответственности (Single Responsibility Principle)", "Не верно. Правильный ответ Принцип подстановки Барбары Лисков (Liskov Substitution Principle)."], ["Принцип открытости / закрытости (Open Closed Principle)", "Не верно. Правильный ответ прин­цип подстановки Барбары Лисков (Liskov Substitution Principle)."], ["Принцип подстановки Барбары Лисков (Liskov Substitution Principle)", "Верно!"], ["Принцип разделения интерфейса (Interface Segregation Principle)", "Не верно. Правильный ответ Принцип подстановки Барбары Лисков (Liskov Substitution Principle)."], ["Принцип инверсии зависимостей (Dependency Inversion Principle)", "Не верно. Правильный ответ Принцип подстановки Барбары Лисков (Liskov Substitution Principle)."] ] }] ] } ================================================ FILE: js-best-practices.js ================================================ #!/usr/bin/env node var fs = require('fs'); const jsBestPractices = require('workshopper-adventure')({ appDir: __dirname, languages: ['en', 'ru', 'pt-br'], header: require('workshopper-adventure/default/header'), footer: require('workshopper-adventure/default/footer'), commands: require('./helpers/customCommands') }); function fileError (id, file) { var error = new Error(id) error.id = id error.exerciseFile = file error.toString = function () { return '[WorkshopperFileError: ' + id + ' @ ' + error.exerciseFile + ']' } return error } jsBestPractices.addAll([ 'get started', 'decompose balancemanager', 'decompose changehandler', 'decompose productinventory', 'switch statement', 'tdd', 'bad practices', 'verify vendingmachine' ]); module.exports = jsBestPractices; ================================================ FILE: npm-shrinkwrap.json ================================================ { "name": "js-best-practices", "version": "1.1.4", "lockfileVersion": 1, "requires": true, "dependencies": { "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "ansi-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz", "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, "assertion-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=" }, "balanced-match": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" }, "brace-expansion": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=", "requires": { "balanced-match": "^0.4.1", "concat-map": "0.0.1" } }, "buffer-shims": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" }, "capture-stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" }, "cardinal": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.7.1.tgz", "integrity": "sha1-LCcM3HUchs6q+ghFnCuzjaKi4pc=", "requires": { "ansicolors": "~0.2.1", "redeyed": "~0.6.0" }, "dependencies": { "ansicolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" } } }, "chai": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "requires": { "assertion-error": "^1.0.1", "deep-eql": "^0.1.3", "type-detect": "^1.0.0" } }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "charm_inheritance-fix": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/charm_inheritance-fix/-/charm_inheritance-fix-1.0.1.tgz", "integrity": "sha1-rZgaoFy+wAhV9BdUlJYYa4Km+To=" }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" }, "colors-tmpl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/colors-tmpl/-/colors-tmpl-1.0.0.tgz", "integrity": "sha1-tgrEr4FlVdnt8a0kczfrMCQbbS4=", "requires": { "colors": "~1.0.2" }, "dependencies": { "colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" } } }, "combined-stream-wait-for-it": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/combined-stream-wait-for-it/-/combined-stream-wait-for-it-1.1.0.tgz", "integrity": "sha1-4EtO6ITNZXFerE5Yqxc2eiy6RoU=", "requires": { "delayed-stream": "~1.0.0" } }, "commander": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=" }, "commandico": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/commandico/-/commandico-2.0.2.tgz", "integrity": "sha1-uJBcCZYfeUrGdu4tYH98CxtQzkE=", "requires": { "explicit": "^0.1.0", "joi": "^6.10.1", "minimist": "^1.1.1" }, "dependencies": { "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { "capture-stack-trace": "^1.0.0" } }, "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "requires": { "ms": "0.7.1" } }, "deep-eql": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", "requires": { "type-detect": "0.1.1" }, "dependencies": { "type-detect": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=" } } }, "deep-extend": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz", "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=" }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "diff": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=" }, "duplexer2": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "requires": { "readable-stream": "~1.1.9" } }, "error-ex": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz", "integrity": "sha1-5ntD8+gsluo6WE/+4Ln8MyXYAtk=", "requires": { "is-arrayish": "^0.2.1" } }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" }, "explicit": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/explicit/-/explicit-0.1.1.tgz", "integrity": "sha1-HM1WyKk1DaLfN9EWwSUjPRUvgRA=", "requires": { "joi": "^6.10.1" } }, "extended-terminal-menu": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/extended-terminal-menu/-/extended-terminal-menu-2.1.4.tgz", "integrity": "sha1-GoKVOkOYQvVDsVS0YxgJ/aMs3hM=", "requires": { "charm_inheritance-fix": "^1.0.1", "duplexer2": "0.0.2", "inherits": "~2.0.0", "resumer": "~0.0.0", "through2": "^0.6.3", "wcstring": "^2.1.0" } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", "requires": { "inherits": "2", "minimatch": "0.3" } }, "got": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "requires": { "create-error-class": "^3.0.1", "duplexer2": "^0.1.4", "is-redirect": "^1.0.0", "is-retry-allowed": "^1.0.0", "is-stream": "^1.0.0", "lowercase-keys": "^1.0.0", "node-status-codes": "^1.0.0", "object-assign": "^4.0.1", "parse-json": "^2.1.0", "pinkie-promise": "^2.0.0", "read-all-stream": "^3.0.0", "readable-stream": "^2.0.5", "timed-out": "^3.0.0", "unzip-response": "^1.0.2", "url-parse-lax": "^1.0.0" }, "dependencies": { "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "requires": { "readable-stream": "^2.0.2" } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", "requires": { "buffer-shims": "^1.0.0", "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } } } }, "growl": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { "ansi-regex": "^2.0.0" } }, "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" }, "i18n-core": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/i18n-core/-/i18n-core-2.1.1.tgz", "integrity": "sha1-l4MCdLPOY+LqZ5YlbgSKSa9WzBQ=", "requires": { "mustache": "^0.8.2", "sprintf": "^0.1.4" } }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "isemail": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=" }, "jade": { "version": "0.26.3", "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", "requires": { "commander": "0.6.1", "mkdirp": "0.3.0" }, "dependencies": { "commander": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" }, "mkdirp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" } } }, "joi": { "version": "6.10.1", "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", "requires": { "hoek": "2.x.x", "isemail": "1.x.x", "moment": "2.x.x", "topo": "1.x.x" } }, "latest-version": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz", "integrity": "sha1-VvjWE5YghHuAF/jx9NeOIRMkFos=", "requires": { "package-json": "^2.0.0" } }, "lowercase-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" }, "lru-cache": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" }, "marked": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=" }, "minimatch": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", "requires": { "lru-cache": "2", "sigmund": "~1.0.0" } }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "mocha": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", "requires": { "commander": "2.3.0", "debug": "2.2.0", "diff": "1.4.0", "escape-string-regexp": "1.0.2", "glob": "3.2.11", "growl": "1.9.2", "jade": "0.26.3", "mkdirp": "0.5.1", "supports-color": "1.2.0", "to-iso-string": "0.0.2" }, "dependencies": { "escape-string-regexp": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=" }, "supports-color": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=" } } }, "moment": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.17.1.tgz", "integrity": "sha1-/tlQYGPzaxDwZsi1mhRNf66+HYI=" }, "ms": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" }, "msee": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/msee/-/msee-0.3.2.tgz", "integrity": "sha1-W/clla7OeP6gy9ow+jIvL0NmwVs=", "requires": { "ansi-regex": "^2.0.0", "ansicolors": "^0.3.2", "cardinal": "^0.7.1", "chalk": "^1.1.1", "marked": "^0.3.5", "nopt": "^3.0.4", "table-header": "^0.2.2", "text-table": "^0.2.0", "wcstring": "^2.1.0", "xtend": "^4.0.0" } }, "mustache": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/mustache/-/mustache-0.8.2.tgz", "integrity": "sha1-v1uSK49Azc+5HANdzZFhENFiH5s=" }, "node-status-codes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "requires": { "abbrev": "1" } }, "object-assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "package-json": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", "integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=", "requires": { "got": "^5.0.0", "registry-auth-token": "^3.0.1", "registry-url": "^3.0.3", "semver": "^5.1.0" } }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { "error-ex": "^1.2.0" } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { "pinkie": "^2.0.0" } }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "rc": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", "integrity": "sha1-Q2UbdrauU7XIAvEVH6P8OwWZack=", "requires": { "deep-extend": "~0.4.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~1.0.4" }, "dependencies": { "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, "read-all-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" }, "dependencies": { "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", "requires": { "buffer-shims": "^1.0.0", "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } } } }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "redeyed": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.6.0.tgz", "integrity": "sha1-aSovdl/sq0M1Se3UOadoSqtn94k=", "requires": { "esprima": "~2.7.0" } }, "registry-auth-token": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.1.0.tgz", "integrity": "sha1-mXwIJW4MeZmDe5DpRNs52KeQJ2s=", "requires": { "rc": "^1.1.6" } }, "registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { "rc": "^1.0.1" } }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "resumer": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", "requires": { "through": "~2.3.4" } }, "rimraf": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", "requires": { "glob": "^7.0.5" }, "dependencies": { "glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.2", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", "requires": { "brace-expansion": "^1.0.0" } } } }, "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, "simple-terminal-menu": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/simple-terminal-menu/-/simple-terminal-menu-1.1.3.tgz", "integrity": "sha1-apqmscQd9T/AsCB4DHZNvN7Egf8=", "requires": { "chalk": "^1.1.1", "extended-terminal-menu": "^2.1.2", "wcstring": "^2.1.0", "xtend": "^4.0.0" } }, "split": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", "integrity": "sha1-xDlc5oOrzSVLwo/h2rtuXCfc/64=", "requires": { "through": "2" } }, "sprintf": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz", "integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=" }, "string-to-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.0.tgz", "integrity": "sha1-rPLJ6tHEGOFIUJoS0su0afMzohg=", "requires": { "inherits": "^2.0.1", "readable-stream": "^2.1.0" }, "dependencies": { "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", "requires": { "buffer-shims": "^1.0.0", "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } } } }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=" }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "table-header": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/table-header/-/table-header-0.2.2.tgz", "integrity": "sha1-fJrbQg6laftHF95dj1xFFIBNLAo=", "requires": { "repeat-string": "^1.5.2" } }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" }, "dependencies": { "readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } } } }, "timed-out": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.0.tgz", "integrity": "sha1-Q7mLFLtxLJFhwo9NwfMGjWegTsI=" }, "to-iso-string": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=" }, "topo": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", "requires": { "hoek": "2.x.x" } }, "tuple-stream": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/tuple-stream/-/tuple-stream-0.0.2.tgz", "integrity": "sha1-+R9vsbr94evXl89gAzeOvDhwuTM=", "requires": { "through": "~2.3.4" } }, "type-detect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=" }, "unzip-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=" }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "requires": { "prepend-http": "^1.0.1" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "varsize-string": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/varsize-string/-/varsize-string-2.2.2.tgz", "integrity": "sha1-7xs7bHLbCDXqL4TN+R/sMMUgaIs=" }, "wcsize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wcsize/-/wcsize-1.0.0.tgz", "integrity": "sha1-qKLhXmqKdHkdulgPaaV9J+hQ6h4=" }, "wcstring": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/wcstring/-/wcstring-2.1.1.tgz", "integrity": "sha1-3tUtdFycceJNCkidKCbSKjZe0Gc=", "requires": { "varsize-string": "^2.2.1", "wcsize": "^1.0.0" } }, "workshopper-adventure": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/workshopper-adventure/-/workshopper-adventure-5.1.6.tgz", "integrity": "sha1-NMhmSt42FyVBfzXb57VCszFqlNY=", "requires": { "after": "^0.8.2", "chalk": "^1.1.3", "colors-tmpl": "~1.0.0", "combined-stream-wait-for-it": "^1.1.0", "commandico": "^2.0.2", "i18n-core": "^2.1.1", "latest-version": "^2.0.0", "msee": "^0.3.2", "simple-terminal-menu": "^1.1.3", "split": "^1.0.0", "string-to-stream": "^1.1.0", "through2": "^2.0.1", "workshopper-adventure-storage": "^3.0.0" }, "dependencies": { "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", "requires": { "buffer-shims": "^1.0.0", "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } }, "through2": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { "readable-stream": "^2.1.5", "xtend": "~4.0.1" } } } }, "workshopper-adventure-storage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/workshopper-adventure-storage/-/workshopper-adventure-storage-3.0.0.tgz", "integrity": "sha1-AXTFsve4DXLJG8Upv70H9HnCY6A=", "requires": { "mkdirp": "^0.5.1", "rimraf": "^2.5.4" } }, "workshopper-exercise": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/workshopper-exercise/-/workshopper-exercise-2.7.0.tgz", "integrity": "sha1-hccAviZkrIWROtqAcEBQQBs+CDY=", "requires": { "after": "~0.8.1", "chalk": "^1.1.1", "i18n-core": "^2.1.1", "split": "^1.0.0", "through2": "^2.0.0", "tuple-stream": "0.0.2", "wcstring": "^2.1.0", "xtend": "^4.0.1" }, "dependencies": { "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", "requires": { "buffer-shims": "^1.0.0", "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "~1.0.0", "process-nextick-args": "~1.0.6", "string_decoder": "~0.10.x", "util-deprecate": "~1.0.1" } }, "through2": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { "readable-stream": "^2.1.5", "xtend": "~4.0.1" } } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } } ================================================ FILE: package.json ================================================ { "name": "js-best-practices", "version": "1.2.0", "description": "Learn the best practices of writing clean JavaScript code.", "main": "run.js", "repository": "https://github.com/excellalabs/js-best-practices-workshopper", "dependencies": { "chai": "^3.5.0", "chalk": "^1.1.3", "colors": "^1.1.2", "mocha": "^2.4.5", "rimraf": "^2.5.4", "simple-terminal-menu": "^1.1.3", "workshopper-adventure": "^5.0.3", "workshopper-exercise": "^2.7.0" }, "preferGlobal": true, "bin": { "js-best-practices": "./bin/run" }, "devDependencies": {}, "scripts": { "start": "node bin/run" }, "keywords": [ "nodeschool", "best practices", "javascript", "workshopper" ], "author": "ExcellaLabs", "license": "ISC" } ================================================ FILE: src/decomposed/balanceManager.js ================================================ var balance = 0; module.exports = { increaseBalance: function(amount){ balance += amount; }, getBalance: function(){ return balance; }, canAfford: function(amount){ if(!this.isValidAmount(amount)){ errorMessage = "Invalid Input"; } if(errorMessage){ throw new Error(errorMessage); } return amount <= balance; }, // This method decreases the balance of the vending machine. If the balance amount is not // enough to cover the purchase, the method throws an error. decreaseBalance: function(amount){ var errorMessage; if(!this.canAfford(amount)){ errorMessage = 'Insufficient balance'; } if(errorMessage){ throw new Error(errorMessage); } balance -= amount; }, isValidAmount: function(amount){ if(amount === null){ return false; } else { return true; } } }; ================================================ FILE: src/decomposed/changeHandler.js ================================================ // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter module.exports = { getAmount: function(coinType) { switch(coinType){ case 'p': return 1; case 'n': return 5; case 'd': return 10; case 'q': return 25; default: throw new Error('Unrecognized coin ' + coinType); } } }; ================================================ FILE: src/decomposed/productInventory.js ================================================ module.exports = { getProducts: function() { return [ { name: 'Skittles', price: 85, id: 'A1' } ]; }, getProduct: function(productId) { switch(productId){ case 'A1': return { name: 'Skittles', price: 85 }; } } }; ================================================ FILE: src/decomposed/vendingMachine.js ================================================ var balanceManager = require('./balanceManager'); var changeHandler = require('./changeHandler'); var productInventory = require('./productInventory'); module.exports = { insertCoin: function(coinType){ var value = changeHandler.getAmount(coinType); balanceManager.increaseBalance(value); }, vendProduct: function(productId){ var product = productInventory.getProduct(productId); balanceManager.decreaseBalance(product.price); return product; }, releaseChange: function(){ var balance = balanceManager.getBalance(); balanceManager.decreaseBalance(balance); return changeHandler.convertToChange(balance); } }; ================================================ FILE: src/expected/balanceManager.js ================================================ var balance = 0; module.exports = { increaseBalance: function(amount){ balance += amount; }, getBalance: function(){ return balance; }, canAfford: function(amount){ return amount <= balance; }, decreaseBalance: function(amount){ if(!this.canAfford(amount)){ throw new Error('Insufficient balance'); } balance -= amount; } }; ================================================ FILE: src/expected/changeHandler.js ================================================ // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter var coins = { 'p': 1, 'n': 5, 'd': 10, 'q': 25 }; var coinsByAmount = ['q', 'd', 'n', 'p']; module.exports = { getAmount: function(coinType) { if(!coins.hasOwnProperty(coinType)){ throw new Error('Unrecognized coin ' + coinType); } return coins[coinType]; }, convertToChange: function(amount) { var change = []; for(var i in coinsByAmount){ var coinType = coinsByAmount[i]; var coinValue = coins[coinType]; while(amount >= coinValue){ change.push(coinType); amount -= coinValue; } } return change; } }; ================================================ FILE: src/expected/productInventory.js ================================================ var products = [ { name: 'Skittles', price: 85, id: 'A1' } ]; module.exports = { getProducts: function() { return products; }, getProduct: function(productId) { var product = products.find(function(p) { return p.id === productId; }); return product; } }; ================================================ FILE: src/expected/vendingMachine.js ================================================ var balanceManager = require('./balanceManager'); var changeHandler = require('./changeHandler'); var productInventory = require('./productInventory'); module.exports = { insertCoin: function(coinType){ var value = changeHandler.getAmount(coinType); balanceManager.increaseBalance(value); }, vendProduct: function(productId){ var product = productInventory.getProduct(productId); balanceManager.decreaseBalance(product.price); return product; }, getProducts: function(){ return productInventory.getProducts(); }, releaseChange: function(){ var balance = balanceManager.getBalance(); balanceManager.decreaseBalance(balance); return changeHandler.convertToChange(balance); } }; ================================================ FILE: src/initial/balanceManager.js ================================================ module.exports = { }; ================================================ FILE: src/initial/changeHandler.js ================================================ module.exports = { }; ================================================ FILE: src/initial/productInventory.js ================================================ module.exports = { }; ================================================ FILE: src/initial/vendingMachine.js ================================================ var balanceManager = require('./balanceManager'); var changeHandler = require('./changeHandler'); var productInventory = require('./productInventory'); var balance = 0; var products = [ { name: 'Skittles', price: 85, id: 'A1' } ]; module.exports = { canAfford: function(amount){ if(!this.isValidAmount(amount)){ errorMessage = "Invalid Input"; } if(errorMessage){ throw new Error(errorMessage); } return amount <= balance; }, decreaseBalance: function(amount){ // This method decreases the balance of the vending machine. If the balance amount is not // enough to cover the purchase, the method throws an error. var errorMessage; if(!this.canAfford(amount)){ errorMessage = 'Insufficient balance'; } if(errorMessage){ throw new Error(errorMessage); } balance -= amount; }, getAmount: function(coinType) { // COINS: // [p]enny // [n]ickel // [d]ime // [q]uarter switch(coinType){ case 'p': return 1; case 'n': return 5; case 'd': return 10; case 'q': return 25; default: throw new Error('Unrecognized coin ' + coinType); } }, getBalance: function(){ return balance; }, getProducts: function() { return products; }, getProduct: function(productId) { var product = products.find(function(p) { return p.id === productId; }); return product; }, increaseBalance: function(amount){ balance += amount; }, insertCoin: function(coinType){ var value = this.getAmount(coinType); this.increaseBalance(value); }, isValidAmount: function(amount){ if(amount === null){ return false; } else { return true; } }, releaseChange: function(){ var currentBalance = this.getBalance(); this.decreaseBalance(currentBalance); return this.convertToChange(currentBalance); }, vendProduct: function(productId){ var product = this.getProduct(productId); this.decreaseBalance(product.price); return product; } };