├── .gitignore ├── .sonarcloud.properties ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── develop.md ├── dist └── upvotejs │ ├── upvotejs.css │ ├── upvotejs.jquery.js │ ├── upvotejs.svg │ └── upvotejs.vanilla.js ├── images ├── feature.png └── forkme_right_red_aa0000.png ├── index.html ├── package-lock.json ├── package.json ├── tests ├── jquery.html ├── lib │ ├── common-tests.js │ ├── setup.js │ └── utils.js ├── test.bootstrap.js ├── test.jquery.js ├── test.vanilla.js └── vanilla.html └── upvote.jquery.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .nyc_output 4 | .coverage 5 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | sonar.sources=dist 3 | #sonar.exclusions= 4 | #sonar.inclusions= 5 | 6 | # Path to tests 7 | sonar.tests=tests 8 | #sonar.test.exclusions= 9 | #sonar.test.inclusions= 10 | 11 | # Source encoding 12 | #sonar.sourceEncoding=UTF-8 13 | 14 | # Exclusions for copy-paste detection 15 | #sonar.cpd.exclusions= 16 | 17 | sonar.projectVersion=2.1.0 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [2.2.0] - 2019-02-26 11 | 12 | ### Changed 13 | 14 | - Replace qunit tests with mocha 15 | 16 | ### Added 17 | 18 | - Add test coverage reporting 19 | - Integrate with Travis CI 20 | - Integrate with sonarcloud.io (autoscan mode) 21 | 22 | ## [2.1.0] - 2019-02-09 23 | 24 | ### Added 25 | 26 | - This changelog (recent history written in retrospect) 27 | - Add user action to JSON payload in callbacks 28 | 29 | ### Changed 30 | 31 | - Move data to newState field in JSON payload in callbacks (API breaking) 32 | 33 | ## [2.0.0] - 2019-02-05 34 | 35 | ### Changed 36 | 37 | - Rewrite in vanilla JavaScript 38 | - Update widget style to match current design on Stack Exchange 39 | - Rewrite entire codebase 40 | 41 | ## [1.0.0] - 2013-07-04 42 | 43 | ### Added 44 | 45 | - Voting widget like the one used on Stack Exchange sites 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UpvoteJS 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/janosgyerik/upvotejs.svg?branch=master)](https://travis-ci.org/janosgyerik/upvotejs) 5 | 6 | `UpvoteJS` is a JavaScript package to create a voting widget that looks like 7 | the one used on [Stack][stackoverflow] [Exchange][superuser] [sites][serverfault]. 8 | For example, like this: 9 | 10 | ![Screenshot featuring stackoverflow.com, superuser.com, serverfault.com][feature] 11 | 12 | Using the package 13 | ----------------- 14 | 15 | There are different ways to use the package, depending on your use case. 16 | 17 | ### Create a "read-only" widget in HTML 18 | 19 | That is, have a nicely rendered widget based on HTML markup alone, 20 | to simply represent some state, that cannot be modified by user action. 21 | In this mode, no JavaScript is needed, and the counter, the upvote/downvote arrows 22 | and the star are rendered based on the HTML markup alone. 23 | 24 | Include the stylesheet in your page's `` element, for example: 25 | 26 | 27 | 28 | Make sure the image `upvotejs.svg` is present in the same directory as the `upvotejs.css` file. 29 | 30 | Include this basic HTML boilerplate for each vote: 31 | 32 |
33 | 34 | 0 35 | 36 | 37 |
38 | 39 | Customize the boilerplate with values appropriate for the vote you want to display: 40 | 41 | - Set the correct value for `.count` 42 | - For upvoted state, add class `upvote-on` for `.upvote` 43 | - For downvoted state, add class `downvote-on` for `.downvote` 44 | - For starred state, add class `star-on` for `.star` 45 | 46 | With only HTML code, the widget is read-only, the voting and star buttons are not clickable. 47 | 48 | ### Create an interactive widget 49 | 50 | That is, enable user interactions to upvote, downvote, or star, 51 | modifying the state of the counter. 52 | However, storing the state on some backend is still out of scope in this mode. 53 | 54 | Include the JavaScript sources in your page's `` element, for example: 55 | 56 | 57 | 58 | Create the Upvote widget controller: 59 | 60 | Upvote.create('id'); 61 | 62 | Where `id` is the ID of the widget in the DOM. 63 | 64 | With this step, the controls of the widget will become clickable, upvoting and downvoting will update the count and indicate toggled state, so will the star, and consistent state will be enforced. 65 | 66 | With HTML and JavaScript code alone, the state of the widget will not be persisted in any storage. 67 | 68 | ### Save the state of the widget to some backend 69 | 70 | In order to save the state of the widget in response to user action, 71 | pass a callback handler when creating the widget, for example: 72 | 73 | Upvote.create('id', {callback: your_callback_handler}); 74 | 75 | On any change to the state of the widget (vote up, vote down, or star), 76 | the callback handler will be called, with a JSON object as parameter, 77 | with fields: 78 | 79 | - `id`: the id of the DOM object, the same value that was used when creating with `Upvote.create` 80 | 81 | - `action`: the user action that triggered the state change. 82 | Possible values: `upvote`, `unupvote`, `downvote`, `undownvote`, `star`, `unstar` 83 | 84 | - `newState`: a JSON object with fields: 85 | - `count`: the current vote count 86 | - `upvoted`: `true` if the widget is in upvoted state 87 | - `downvoted`: `true` if the widget is in downvoted state 88 | - `starred`: `true` if the widget is in starred state 89 | 90 | Note that `upvoted` and `downvoted` will never be `true` at the same time. 91 | 92 | An example payload: 93 | 94 | { 95 | id: 'my-vote', 96 | action: 'upvote', 97 | newState: { 98 | count: 123, 99 | upvoted: true, 100 | downvoted: false, 101 | starred: true 102 | } 103 | } 104 | 105 | Using this data object, it is up to your implementation of `your_callback_handler` to actually implement writing the new state on some backend, for example with a `POST` or `PATCH` call to a storage service to update the user's vote. 106 | 107 | ### Using with jQuery 108 | 109 | It's possible to use the package through jQuery, if you prefer (though not clear why). 110 | 111 | Include the following in ``: 112 | 113 | 114 | 115 | 116 | 117 | Make sure the image `upvotejs.svg` is present in the same directory as the `upvotejs.css` file. 118 | 119 | Initialization examples: 120 | 121 | $('#topic').upvote(); 122 | $('#topic').upvote({count: 5, upvoted: true}); 123 | $('#topic').upvote({count: 5, downvoted: true}); 124 | $('#topic').upvote({count: 5, upvoted: true, starred: true}); 125 | 126 | var callback = function(data) { 127 | $.ajax({ 128 | url: '/vote', 129 | type: 'post', 130 | data: data 131 | }); 132 | }; 133 | $('#topic-123').upvote({id: 123, callback: callback}); 134 | 135 | Methods: 136 | 137 | // Create, pick up initial values from HTML markup 138 | $('#topic').upvote(); 139 | 140 | // Mutators 141 | $('#topic').upvote('upvote'); // Upvote! 142 | $('#topic').upvote('downvote'); // Downvote! 143 | $('#topic').upvote('star'); // Star! 144 | 145 | // Getters 146 | $('#topic').upvote('count'); // Get the current vote count 147 | $('#topic').upvote('upvoted'); // Get the upvoted state -> boolean 148 | $('#topic').upvote('downvoted'); // Get the downvoted state -> boolean 149 | $('#topic').upvote('starred'); // Get the starred state -> boolean 150 | 151 | API reference 152 | ------------- 153 | 154 | Files: 155 | 156 | - `upvotejs.css` and `upvotejs.svg` are required for styling, and both must be in the same directory 157 | - `upvotejs.vanilla.js` is required for interactive use 158 | 159 | Create an Upvote widget controller using `Upvote.create`: 160 | 161 | const widget = Upvote.create(id, params = {}); 162 | 163 | An element in the DOM must exist with the specified `id`, and have a basic markup like this: 164 | 165 |
166 | 167 | 0 168 | 169 | 170 |
171 | 172 | The widget will be parameterized based on the markup of the DOM element: 173 | 174 | - The count is read from the value of `.count` 175 | - The state is considered upvoted if `.upvote` has `upvote-on` class. 176 | - The state is considered downvoted if `.downvote` has `downvote-on` class. 177 | - The state is considered starred if `.star` has `star-on` class. 178 | 179 | An exception will be thrown in case of invalid DOM markup: 180 | 181 | - If a DOM element with the specified `id` doesn't exist 182 | - If the DOM element with `id` is already used by another Upvote widget controller 183 | - If the DOM element doesn't have all required components 184 | - If both upvoted and downvoted state are detected at the same time 185 | 186 | The state values can be overridden explicitly using the `param` object, with fields: 187 | 188 | - `count` - the vote count to set, must be an integer 189 | - `upvoted` - a boolean to set upvoted state 190 | - `downvoted` - a boolean to set downvoted state 191 | - `starred` - a boolean to set starred state 192 | - `callback` - a function that will be called on any state change 193 | 194 | An exception will be thrown in case of invalid parameters: 195 | 196 | - If the parameter types don't match expected values 197 | - If `upvoted` and `downvoted` are both `true` 198 | 199 | ### Properties 200 | 201 | An instance of an Upvote widget controller has the following properties: 202 | 203 | - `id`: the `id` of the controller 204 | - `count()`: get the current count 205 | - `upvote()`: toggle upvoted state 206 | - `upvoted()`: get the upvoted state 207 | - `downvote()`: toggle downvoted state 208 | - `downvoted()`: get the downvoted state 209 | - `star()`: toggle starred state 210 | - `starred()`: get the starred state 211 | 212 | ### Event handling 213 | 214 | The widget controller, on its creation, overwrites the `onclick` property of the selected DOM. 215 | 216 | On `destroy`, the widget controller resets `onclick` to `null`. 217 | 218 | Sponsors 219 | -------- 220 | 221 | Contributions from users and sponsors help make UpvoteJS possible. 222 | We accept donations via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SQTLZB5QCLR82). Thanks! :) 223 | 224 | License 225 | ------- 226 | 227 | Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 Unported License. 228 | 229 | [stackoverflow]: http://stackoverflow.com/ 230 | [superuser]: http://superuser.com/ 231 | [serverfault]: http://serverfault.com/ 232 | [feature]: images/feature.png 233 | -------------------------------------------------------------------------------- /develop.md: -------------------------------------------------------------------------------- 1 | Develop 2 | ======= 3 | 4 | Practical tips and notes for working on this package. 5 | 6 | Setup 7 | ----- 8 | 9 | Install dependencies: 10 | 11 | npm install 12 | 13 | Verify that all automated tests are passing: 14 | 15 | npm test 16 | 17 | Optionally, open the unit tests in a browser: 18 | 19 | open tests/vanilla.html 20 | open tests/jquery.html 21 | 22 | 23 | Importing from another new Stack Exchange site 24 | ---------------------------------------------- 25 | 26 | Visit the site you want to import from. 27 | 28 | Toggle an upvote or downvote to activate the color of the site, 29 | and inspect the source of the image using your favorite browser Developer Console. 30 | 31 | Look at computed CSS properties, and find the value of `filter`. 32 | 33 | Copy that value and add a CSS entry following this pattern: 34 | 35 | div.upvote-abc a.upvote.upvote-on, div.upvote-abc a.downvote-on { 36 | filter: ... 37 | } 38 | 39 | Updating to new style 40 | --------------------- 41 | 42 | Sometimes the Stack Exchange sites get a facelift. 43 | These were the steps I took last time I need to update the styles, in late 2018. 44 | 45 | Inspect the source of any upvote icon on stackoverflow.com 46 | 47 | Find the `background-image`, for example https://cdn.sstatic.net/Img/unified/sprites.svg?v=e5e58ae7df45 for Stack Overflow as of today. 48 | 49 | Download the background image into `lib/images/sprites-stackoverflow.svg` 50 | 51 | Review and update CSS attributes as necessary, typically: 52 | 53 | - `width` 54 | - `height` 55 | - `font-size` 56 | - `font-family` 57 | - `color` 58 | - `margin` 59 | - `background-position` 60 | - `filter` 61 | 62 | Publishing the jQuery plugin 63 | ---------------------------- 64 | 65 | See https://plugins.jquery.com/docs/publish/ 66 | 67 | Published at https://plugins.jquery.com/upvote/ 68 | -------------------------------------------------------------------------------- /dist/upvotejs/upvotejs.css: -------------------------------------------------------------------------------- 1 | div.upvotejs { 2 | text-align: center; 3 | } 4 | 5 | div.upvotejs a { 6 | cursor: default; 7 | color: transparent; 8 | background-image: url('upvotejs.svg?v=1'); 9 | background-repeat: no-repeat; 10 | overflow: hidden; 11 | display: block; 12 | margin: 0 auto; 13 | margin-bottom: 2px; 14 | width: 40px; 15 | height: 30px; 16 | } 17 | 18 | div.upvotejs-enabled a { 19 | cursor: pointer; 20 | } 21 | 22 | div.upvotejs span.count { 23 | display: block; 24 | text-align: center; 25 | font-size: 20px; 26 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; 27 | color: #6a737c; 28 | margin: 8px 0; 29 | } 30 | 31 | div.upvotejs a.upvote { 32 | background-position: 0px -170px; 33 | } 34 | 35 | div.upvotejs a.upvote.upvote-on { 36 | background-position: -40px -170px; 37 | } 38 | 39 | div.upvotejs a.downvote { 40 | background-position: 0px -220px; 41 | } 42 | 43 | div.upvotejs a.downvote.downvote-on { 44 | background-position: -40px -220px; 45 | } 46 | 47 | div.upvotejs a.star { 48 | background-position: 0px -120px; 49 | } 50 | 51 | div.upvotejs a.star.star-on { 52 | background-position: -40px -120px; 53 | } 54 | 55 | div.upvotejs-unix a.upvote.upvote-on, div.upvotejs-unix a.downvote-on { 56 | filter: invert(.5) sepia(1) hue-rotate(165.7518582deg) saturate(3.8235864) hue-rotate(-0.81675376deg) brightness(.47739161); 57 | } 58 | 59 | div.upvotejs-serverfault a.upvote.upvote-on, div.upvotejs-serverfault a.downvote-on { 60 | filter: invert(.5) sepia(1) hue-rotate(315.5792014deg) saturate(7.91709017) hue-rotate(346.04745219deg) brightness(.57640233); 61 | } 62 | 63 | div.upvotejs-superuser a.upvote.upvote-on, div.upvotejs-superuser a.downvote-on { 64 | filter: invert(.5) sepia(1) hue-rotate(159.50943396deg) saturate(2.94246645) hue-rotate(-4.96258652deg) brightness(.925639); 65 | } 66 | -------------------------------------------------------------------------------- /dist/upvotejs/upvotejs.jquery.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Upvote - a voting plugin 3 | * ------------------------------------------------------------------ 4 | * 5 | * jQuery Upvote is a plugin that generates a voting widget like 6 | * the one used on Stack Exchange sites. 7 | * 8 | * Licensed under Creative Commons Attribution 3.0 Unported 9 | * http://creativecommons.org/licenses/by/3.0/ 10 | * 11 | * @version 2.1.0 12 | * @since 2013.06.19 13 | * @author Janos Gyerik 14 | * @homepage https://janosgyerik.github.io/upvotejs 15 | * @twitter twitter.com/janosgyerik 16 | * 17 | * ------------------------------------------------------------------ 18 | * 19 | *
20 | * 21 | * 22 | * 23 | * 24 | *
25 | * 26 | * $('#topic').upvote(); 27 | * $('#topic').upvote({count: 5, upvoted: true}); 28 | * 29 | */ 30 | 31 | ;(function($) { 32 | "use strict"; 33 | const namespace = 'upvotejs'; 34 | const enabledClass = 'upvotejs-enabled'; 35 | 36 | function init(dom, options) { 37 | const created = []; 38 | try { 39 | var ret = dom.each(function() { 40 | const jqdom = $(this); 41 | const id = jqdom.attr('id'); 42 | const obj = Upvote.create(id, options); 43 | jqdom.data(namespace, obj); 44 | created.push(jqdom); 45 | }); 46 | } catch (e) { 47 | created.forEach(obj => methods.destroy(obj)); 48 | throw e; 49 | } 50 | return ret; 51 | } 52 | 53 | function upvote(jqdom) { 54 | jqdom.data(namespace).upvote(); 55 | return jqdom; 56 | } 57 | 58 | function downvote(jqdom) { 59 | jqdom.data(namespace).downvote(); 60 | return jqdom; 61 | } 62 | 63 | function star(jqdom) { 64 | jqdom.data(namespace).star(); 65 | return jqdom; 66 | } 67 | 68 | function count(jqdom) { 69 | return jqdom.data(namespace).count(); 70 | } 71 | 72 | function upvoted(jqdom) { 73 | return jqdom.data(namespace).upvoted(); 74 | } 75 | 76 | function downvoted(jqdom) { 77 | return jqdom.data(namespace).downvoted(); 78 | } 79 | 80 | function starred(jqdom) { 81 | return jqdom.data(namespace).starred(); 82 | } 83 | 84 | const methods = { 85 | init: init, 86 | count: count, 87 | upvote: upvote, 88 | upvoted: upvoted, 89 | downvote: downvote, 90 | downvoted: downvoted, 91 | starred: starred, 92 | star: star, 93 | destroy: destroy 94 | }; 95 | 96 | function destroy(jqdom) { 97 | return jqdom.each(function() { 98 | const obj = jqdom.data(namespace); 99 | if (obj) { 100 | obj.destroy(); 101 | } 102 | $(this).removeClass(enabledClass); 103 | $(this).removeData(namespace); 104 | }); 105 | } 106 | 107 | $.fn.upvote = function(method) { 108 | var args; 109 | if (methods[method]) { 110 | args = Array.prototype.slice.call(arguments, 1); 111 | args.unshift(this); 112 | return methods[method].apply(this, args); 113 | } 114 | if (typeof method === 'object' || ! method) { 115 | args = Array.prototype.slice.call(arguments); 116 | args.unshift(this); 117 | return methods.init.apply(this, args); 118 | } 119 | $.error('Method ' + method + ' does not exist on jQuery.upvote'); 120 | }; 121 | })(jQuery); 122 | -------------------------------------------------------------------------------- /dist/upvotejs/upvotejs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/upvotejs/upvotejs.vanilla.js: -------------------------------------------------------------------------------- 1 | /* 2 | * UpvoteJS - a Stack Exchange look-alike voting widget 3 | * ---------------------------------------------------- 4 | * 5 | * UpvoteJS is a widget that generates a voting widget like 6 | * the one used on Stack Exchange sites. 7 | * 8 | * Licensed under Creative Commons Attribution 3.0 Unported 9 | * http://creativecommons.org/licenses/by/3.0/ 10 | * 11 | * @version 2.1.0 12 | * @since 2018.12.05 13 | * @author Janos Gyerik 14 | * @homepage https://janosgyerik.github.io/upvotejs 15 | * @twitter twitter.com/janosgyerik 16 | * 17 | * ------------------------------------------------------------------ 18 | * 19 | *
20 | * 21 | * 22 | * 23 | * 24 | *
25 | * 26 | * Upvote.create('id'); 27 | * Upvote.create('id', {count: 5, upvoted: true}); 28 | * 29 | */ 30 | 31 | const UpvoteJS = function(document) { 32 | "use strict"; 33 | 34 | const enabledClass = 'upvotejs-enabled'; 35 | const upvoteClass = 'upvote'; 36 | const upvoteOnClass = 'upvote-on'; 37 | const downvoteClass = 'downvote'; 38 | const downvoteOnClass = 'downvote-on'; 39 | const starClass = 'star'; 40 | const starOnClass = 'star-on'; 41 | const countClass = 'count'; 42 | 43 | const Utils = { 44 | combine: function() { 45 | const combined = {}; 46 | for (let i = 0; i < arguments.length; i++) { 47 | Object.entries(arguments[i]) 48 | .filter(e => e[1] !== undefined) 49 | .forEach(e => combined[e[0]] = e[1]); 50 | } 51 | 52 | return combined; 53 | }, 54 | isBoolean: v => typeof v === "boolean", 55 | isFunction: v => typeof v === "function", 56 | classes: dom => dom.className.split(/ +/).filter(x => x), 57 | removeClass: (dom, className) => { 58 | dom.className = dom.className.split(/ +/) 59 | .filter(x => x) 60 | .filter(c => c !== className) 61 | .join(' '); 62 | }, 63 | noop: () => {} 64 | }; 65 | 66 | const Model = function() { 67 | const validate = params => { 68 | if (!Number.isInteger(params.count)) { 69 | throw 'error: parameter "count" must be a valid integer'; 70 | } 71 | if (!Utils.isBoolean(params.upvoted)) { 72 | throw 'error: parameter "upvoted" must be a boolean'; 73 | } 74 | if (!Utils.isBoolean(params.downvoted)) { 75 | throw 'error: parameter "downvoted" must be a boolean'; 76 | } 77 | if (!Utils.isBoolean(params.starred)) { 78 | throw 'error: parameter "starred" must be a boolean'; 79 | } 80 | if (params.callback && !Utils.isFunction(params.callback)) { 81 | throw 'error: parameter "callback" must be a function'; 82 | } 83 | if (params.upvoted && params.downvoted) { 84 | throw 'error: parameters "upvoted" and "downvoted" must not be true at the same time'; 85 | } 86 | }; 87 | 88 | const create = params => { 89 | validate(params); 90 | 91 | const data = Utils.combine(params); 92 | 93 | const upvote = () => { 94 | if (data.upvoted) { 95 | data.count--; 96 | } else { 97 | data.count++; 98 | if (data.downvoted) { 99 | data.downvoted = false; 100 | data.count++; 101 | } 102 | } 103 | data.upvoted = !data.upvoted; 104 | }; 105 | 106 | const downvote = () => { 107 | if (data.downvoted) { 108 | data.count++; 109 | } else { 110 | data.count--; 111 | if (data.upvoted) { 112 | data.upvoted = false; 113 | data.count--; 114 | } 115 | } 116 | data.downvoted = !data.downvoted; 117 | }; 118 | 119 | return { 120 | count: () => data.count, 121 | upvote: upvote, 122 | upvoted: () => data.upvoted, 123 | downvote: downvote, 124 | downvoted: () => data.downvoted, 125 | star: () => data.starred = !data.starred, 126 | starred: () => data.starred, 127 | data: () => Utils.combine(data) 128 | }; 129 | }; 130 | 131 | return { 132 | create: create 133 | }; 134 | }(); 135 | 136 | const View = function() { 137 | const create = id => { 138 | const dom = document.getElementById(id); 139 | if (dom === null) { 140 | throw 'error: element with ID "' + id + '" must exist in the DOM'; 141 | } 142 | 143 | if (Utils.classes(dom).includes(enabledClass)) { 144 | throw 'error: element with ID ' + id + ' is already in use by another upvote controller'; 145 | } 146 | dom.className += ' ' + enabledClass; 147 | 148 | const firstElementByClass = className => { 149 | return dom.getElementsByClassName(className)[0]; 150 | }; 151 | 152 | const createCounter = className => { 153 | const dom = firstElementByClass(className); 154 | 155 | if (dom === undefined) { 156 | return { 157 | count: () => undefined, 158 | set: Utils.noop 159 | }; 160 | } 161 | 162 | return { 163 | count: () => parseInt(dom.innerHTML || 0, 10), 164 | set: value => dom.innerHTML = value 165 | }; 166 | }; 167 | 168 | const createToggle = (className, activeClassName) => { 169 | const createClasses = () => { 170 | const classes = { 171 | [className]: true, 172 | [activeClassName]: false, 173 | }; 174 | item.className.split(/ +/) 175 | .filter(x => x) 176 | .forEach(className => classes[className] = true); 177 | return classes; 178 | }; 179 | 180 | const formatClassName = () => { 181 | return Object.entries(classes) 182 | .filter(e => e[1]) 183 | .map(e => e[0]) 184 | .join(' '); 185 | }; 186 | 187 | const item = firstElementByClass(className); 188 | if (item === undefined) { 189 | return { 190 | get: () => false, 191 | set: Utils.noop, 192 | onClick: Utils.noop 193 | }; 194 | } 195 | 196 | const classes = createClasses(); 197 | 198 | return { 199 | get: () => classes[activeClassName], 200 | set: value => { 201 | classes[activeClassName] = value; 202 | item.className = formatClassName(); 203 | }, 204 | onClick: fun => item.onclick = fun 205 | }; 206 | }; 207 | 208 | const render = model => { 209 | counter.set(model.count()); 210 | upvote.set(model.upvoted()); 211 | downvote.set(model.downvoted()); 212 | star.set(model.starred()); 213 | }; 214 | 215 | const parseParamsFromDom = () => { 216 | return { 217 | count: counter.count(), 218 | upvoted: upvote.get(), 219 | downvoted: downvote.get(), 220 | starred: star.get() 221 | }; 222 | }; 223 | 224 | const destroy = () => { 225 | Utils.removeClass(dom, enabledClass); 226 | upvote.onClick(null); 227 | downvote.onClick(null); 228 | star.onClick(null); 229 | }; 230 | 231 | const counter = createCounter(countClass); 232 | const upvote = createToggle(upvoteClass, upvoteOnClass); 233 | const downvote = createToggle(downvoteClass, downvoteOnClass); 234 | const star = createToggle(starClass, starOnClass); 235 | 236 | return { 237 | render: render, 238 | parseParamsFromDom: parseParamsFromDom, 239 | onClickUpvote: fun => upvote.onClick(fun), 240 | onClickDownvote: fun => downvote.onClick(fun), 241 | onClickStar: fun => star.onClick(fun), 242 | destroy: destroy 243 | }; 244 | }; 245 | 246 | return { 247 | create: create 248 | }; 249 | }(); 250 | 251 | const create = (id, params = {}) => { 252 | var destroyed = false; 253 | const view = View.create(id); 254 | const domParams = view.parseParamsFromDom(); 255 | const defaults = { 256 | id: id, 257 | count: 0, 258 | upvoted: false, 259 | downvoted: false, 260 | starred: false, 261 | callback: () => {} 262 | }; 263 | const combinedParams = Utils.combine(defaults, domParams, params); 264 | const model = Model.create(combinedParams); 265 | 266 | const throwIfDestroyed = () => { 267 | if (destroyed) { 268 | throw "fatal: unexpected call to destroyed controller"; 269 | } 270 | }; 271 | 272 | const callback = action => { 273 | const data = model.data(); 274 | combinedParams.callback({ 275 | id: id, 276 | action: action, 277 | newState: { 278 | count: data.count, 279 | upvoted: data.upvoted, 280 | downvoted: data.downvoted, 281 | starred: data.starred 282 | } 283 | }); 284 | }; 285 | 286 | const upvote = () => { 287 | throwIfDestroyed(); 288 | model.upvote(); 289 | view.render(model); 290 | callback(model.upvoted() ? 'upvote' : 'unupvote'); 291 | }; 292 | 293 | const downvote = () => { 294 | throwIfDestroyed(); 295 | model.downvote(); 296 | view.render(model); 297 | callback(model.downvoted() ? 'downvote' : 'undownvote'); 298 | }; 299 | 300 | const star = () => { 301 | throwIfDestroyed(); 302 | model.star(); 303 | view.render(model); 304 | callback(model.starred() ? 'star' : 'unstar'); 305 | }; 306 | 307 | const destroy = () => { 308 | throwIfDestroyed(); 309 | destroyed = true; 310 | view.destroy(); 311 | }; 312 | 313 | view.render(model); 314 | view.onClickUpvote(upvote); 315 | view.onClickDownvote(downvote); 316 | view.onClickStar(star); 317 | 318 | return { 319 | id: id, 320 | count: () => { 321 | throwIfDestroyed(); 322 | return model.count(); 323 | }, 324 | upvote: upvote, 325 | upvoted: () => { 326 | throwIfDestroyed(); 327 | return model.upvoted(); 328 | }, 329 | downvote: downvote, 330 | downvoted: () => { 331 | throwIfDestroyed(); 332 | return model.downvoted(); 333 | }, 334 | star: star, 335 | starred: () => { 336 | throwIfDestroyed(); 337 | return model.starred(); 338 | }, 339 | destroy: destroy 340 | }; 341 | }; 342 | 343 | return { 344 | create: create 345 | }; 346 | }; 347 | 348 | (function(global, factory) { 349 | "use strict"; 350 | 351 | if (typeof module === "object" && typeof module.exports === "object") { 352 | const create = () => { 353 | if (global.document) { 354 | return factory(global, true); 355 | } 356 | return w => { 357 | if (!w.document) { 358 | throw new Error("UpvoteJS requires a window with a document"); 359 | } 360 | return factory(w); 361 | }; 362 | }; 363 | module.exports = create(); 364 | } else { 365 | factory(global); 366 | } 367 | })(typeof window !== "undefined" ? window : this, function(window, noGlobal) { 368 | const Upvote = UpvoteJS(window.document); 369 | if (!noGlobal) { 370 | window.Upvote = Upvote; 371 | } 372 | return Upvote; 373 | }); 374 | -------------------------------------------------------------------------------- /images/feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janosgyerik/upvotejs/b80cff6b8844f14f3de86c3cfd8b1fb4f130d97e/images/feature.png -------------------------------------------------------------------------------- /images/forkme_right_red_aa0000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janosgyerik/upvotejs/b80cff6b8844f14f3de86c3cfd8b1fb4f130d97e/images/forkme_right_red_aa0000.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UpvoteJS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 34 | 35 | 36 | Fork me on GitHub 37 |
38 |

UpvoteJS - a voting widget

39 |
UpvoteJS is a JavaScript package to create a voting widget like the one used on Stack Exchange sites.
40 | 41 |

Examples

42 |
43 |
44 |

stackoverflow.com

45 |
46 |
47 |
48 |

unix.stackexchange.com

49 |
50 |
51 |
52 | 53 |
54 |
55 |

superuser.com

56 |
57 |
58 |
59 |

serverfault.com

60 |
61 |
62 |
63 | 64 |

Markup (read-only examples)

65 |
66 |
67 |

Basic

68 |
69 |
70 |
71 | 72 | 5 73 | 74 | 75 |
76 |
77 |
78 |
 79 | <div id="basic" class="upvotejs">
 80 |     <a class="upvote"></a>
 81 |     <span class="count">5</span>
 82 |     <a class="downvote"></a>
 83 |     <a class="star"></a>
 84 | </div>
85 |
86 |
87 | 99 |
100 |
101 |

Upvoted

102 |
103 |
104 |
105 | 106 | 6 107 | 108 | 109 |
110 |
111 |
112 |
113 | <div id="upvoted" class="upvotejs">
114 |     <a class="upvote upvote-on"></a>
115 |     <span class="count">6</span>
116 |     <a class="downvote"></a>
117 |     <a class="star"></a>
118 | </div>
119 |
120 |
121 | 133 |
134 |
135 |
136 |
137 |

Downvoted

138 |
139 |
140 |
141 | 142 | 4 143 | 144 | 145 |
146 |
147 |
148 |
149 | <div id="downvoted" class="upvotejs">
150 |     <a class="upvote"></a>
151 |     <span class="count">4</span>
152 |     <a class="downvote downvote-on"></a>
153 |     <a class="star"></a>
154 | </div>
155 |
156 |
157 | 169 |
170 |
171 |

Upvoted and starred

172 |
173 |
174 |
175 | 176 | 6 177 | 178 | 179 |
180 |
181 |
182 |
183 | <div id="upvoted-and-starred" class="upvotejs">
184 |     <a class="upvote upvote-on"></a>
185 |     <span class="count">6</span>
186 |     <a class="downvote"></a>
187 |     <a class="star star-on"></a>
188 | </div>
189 |
190 |
191 | 203 |
204 |
205 |
206 |
207 |

unix.stackexchange.com style

208 |
209 |
210 |
211 | 212 | 4 213 | 214 | 215 |
216 |
217 |
218 |
219 | <div id="unix" class="upvotejs upvotejs-unix">
220 |     <a class="upvote"></a>
221 |     <span class="count">4</span>
222 |     <a class="downvote downvote-on"></a>
223 |     <a class="star"></a>
224 | </div>
225 |
226 |
227 | 239 |
240 |
241 |

serverfault.com style

242 |
243 |
244 |
245 | 246 | 6 247 | 248 | 249 |
250 |
251 |
252 |
253 | <div id="serverfault" class="upvotejs upvotejs-serverfault">
254 |     <a class="upvote upvote-on"></a>
255 |     <span class="count">6</span>
256 |     <a class="downvote"></a>
257 |     <a class="star star-on"></a>
258 | </div>
259 |
260 |
261 | 273 |
274 |
275 |

Links

276 | 281 |
282 | 287 | 295 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upvotejs", 3 | "version": "2.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.0.0", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", 10 | "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", 11 | "requires": { 12 | "@babel/highlight": "^7.0.0" 13 | } 14 | }, 15 | "@babel/generator": { 16 | "version": "7.3.4", 17 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", 18 | "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", 19 | "requires": { 20 | "@babel/types": "^7.3.4", 21 | "jsesc": "^2.5.1", 22 | "lodash": "^4.17.11", 23 | "source-map": "^0.5.0", 24 | "trim-right": "^1.0.1" 25 | } 26 | }, 27 | "@babel/helper-function-name": { 28 | "version": "7.1.0", 29 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", 30 | "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", 31 | "requires": { 32 | "@babel/helper-get-function-arity": "^7.0.0", 33 | "@babel/template": "^7.1.0", 34 | "@babel/types": "^7.0.0" 35 | } 36 | }, 37 | "@babel/helper-get-function-arity": { 38 | "version": "7.0.0", 39 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", 40 | "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", 41 | "requires": { 42 | "@babel/types": "^7.0.0" 43 | } 44 | }, 45 | "@babel/helper-split-export-declaration": { 46 | "version": "7.0.0", 47 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", 48 | "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", 49 | "requires": { 50 | "@babel/types": "^7.0.0" 51 | } 52 | }, 53 | "@babel/highlight": { 54 | "version": "7.0.0", 55 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", 56 | "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", 57 | "requires": { 58 | "chalk": "^2.0.0", 59 | "esutils": "^2.0.2", 60 | "js-tokens": "^4.0.0" 61 | } 62 | }, 63 | "@babel/parser": { 64 | "version": "7.3.4", 65 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", 66 | "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" 67 | }, 68 | "@babel/template": { 69 | "version": "7.2.2", 70 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", 71 | "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", 72 | "requires": { 73 | "@babel/code-frame": "^7.0.0", 74 | "@babel/parser": "^7.2.2", 75 | "@babel/types": "^7.2.2" 76 | } 77 | }, 78 | "@babel/traverse": { 79 | "version": "7.3.4", 80 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", 81 | "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", 82 | "requires": { 83 | "@babel/code-frame": "^7.0.0", 84 | "@babel/generator": "^7.3.4", 85 | "@babel/helper-function-name": "^7.1.0", 86 | "@babel/helper-split-export-declaration": "^7.0.0", 87 | "@babel/parser": "^7.3.4", 88 | "@babel/types": "^7.3.4", 89 | "debug": "^4.1.0", 90 | "globals": "^11.1.0", 91 | "lodash": "^4.17.11" 92 | } 93 | }, 94 | "@babel/types": { 95 | "version": "7.3.4", 96 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", 97 | "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", 98 | "requires": { 99 | "esutils": "^2.0.2", 100 | "lodash": "^4.17.11", 101 | "to-fast-properties": "^2.0.0" 102 | } 103 | }, 104 | "abab": { 105 | "version": "2.0.0", 106 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", 107 | "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", 108 | "dev": true 109 | }, 110 | "acorn": { 111 | "version": "6.1.0", 112 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", 113 | "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", 114 | "dev": true 115 | }, 116 | "acorn-globals": { 117 | "version": "4.3.0", 118 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", 119 | "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", 120 | "dev": true, 121 | "requires": { 122 | "acorn": "^6.0.1", 123 | "acorn-walk": "^6.0.1" 124 | } 125 | }, 126 | "acorn-walk": { 127 | "version": "6.1.1", 128 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", 129 | "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", 130 | "dev": true 131 | }, 132 | "ajv": { 133 | "version": "6.9.1", 134 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", 135 | "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", 136 | "dev": true, 137 | "requires": { 138 | "fast-deep-equal": "^2.0.1", 139 | "fast-json-stable-stringify": "^2.0.0", 140 | "json-schema-traverse": "^0.4.1", 141 | "uri-js": "^4.2.2" 142 | } 143 | }, 144 | "ansi-styles": { 145 | "version": "3.2.1", 146 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 147 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 148 | "requires": { 149 | "color-convert": "^1.9.0" 150 | } 151 | }, 152 | "array-equal": { 153 | "version": "1.0.0", 154 | "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", 155 | "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", 156 | "dev": true 157 | }, 158 | "asn1": { 159 | "version": "0.2.4", 160 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 161 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 162 | "dev": true, 163 | "requires": { 164 | "safer-buffer": "~2.1.0" 165 | } 166 | }, 167 | "assert-plus": { 168 | "version": "1.0.0", 169 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 170 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", 171 | "dev": true 172 | }, 173 | "assertion-error": { 174 | "version": "1.1.0", 175 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 176 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 177 | "dev": true 178 | }, 179 | "async-limiter": { 180 | "version": "1.0.0", 181 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 182 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", 183 | "dev": true 184 | }, 185 | "asynckit": { 186 | "version": "0.4.0", 187 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 188 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 189 | "dev": true 190 | }, 191 | "aws-sign2": { 192 | "version": "0.7.0", 193 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 194 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", 195 | "dev": true 196 | }, 197 | "aws4": { 198 | "version": "1.8.0", 199 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 200 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", 201 | "dev": true 202 | }, 203 | "balanced-match": { 204 | "version": "1.0.0", 205 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 206 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 207 | "dev": true 208 | }, 209 | "bcrypt-pbkdf": { 210 | "version": "1.0.2", 211 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 212 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 213 | "dev": true, 214 | "requires": { 215 | "tweetnacl": "^0.14.3" 216 | } 217 | }, 218 | "brace-expansion": { 219 | "version": "1.1.11", 220 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 221 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 222 | "dev": true, 223 | "requires": { 224 | "balanced-match": "^1.0.0", 225 | "concat-map": "0.0.1" 226 | } 227 | }, 228 | "browser-process-hrtime": { 229 | "version": "0.1.3", 230 | "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", 231 | "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", 232 | "dev": true 233 | }, 234 | "browser-stdout": { 235 | "version": "1.3.1", 236 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 237 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 238 | "dev": true 239 | }, 240 | "caseless": { 241 | "version": "0.12.0", 242 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 243 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", 244 | "dev": true 245 | }, 246 | "chai": { 247 | "version": "4.2.0", 248 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", 249 | "integrity": "sha1-dgqnLPION5XoSxKHfODoNzeqKeU=", 250 | "dev": true, 251 | "requires": { 252 | "assertion-error": "^1.1.0", 253 | "check-error": "^1.0.2", 254 | "deep-eql": "^3.0.1", 255 | "get-func-name": "^2.0.0", 256 | "pathval": "^1.1.0", 257 | "type-detect": "^4.0.5" 258 | } 259 | }, 260 | "chalk": { 261 | "version": "2.4.2", 262 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 263 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 264 | "requires": { 265 | "ansi-styles": "^3.2.1", 266 | "escape-string-regexp": "^1.0.5", 267 | "supports-color": "^5.3.0" 268 | } 269 | }, 270 | "check-error": { 271 | "version": "1.0.2", 272 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 273 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 274 | "dev": true 275 | }, 276 | "color-convert": { 277 | "version": "1.9.3", 278 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 279 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 280 | "requires": { 281 | "color-name": "1.1.3" 282 | } 283 | }, 284 | "color-name": { 285 | "version": "1.1.3", 286 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 287 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 288 | }, 289 | "combined-stream": { 290 | "version": "1.0.7", 291 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", 292 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 293 | "dev": true, 294 | "requires": { 295 | "delayed-stream": "~1.0.0" 296 | } 297 | }, 298 | "concat-map": { 299 | "version": "0.0.1", 300 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 301 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 302 | "dev": true 303 | }, 304 | "core-util-is": { 305 | "version": "1.0.2", 306 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 307 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 308 | "dev": true 309 | }, 310 | "cssom": { 311 | "version": "0.3.6", 312 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", 313 | "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", 314 | "dev": true 315 | }, 316 | "cssstyle": { 317 | "version": "1.2.1", 318 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", 319 | "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", 320 | "dev": true, 321 | "requires": { 322 | "cssom": "0.3.x" 323 | } 324 | }, 325 | "dashdash": { 326 | "version": "1.14.1", 327 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 328 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 329 | "dev": true, 330 | "requires": { 331 | "assert-plus": "^1.0.0" 332 | } 333 | }, 334 | "data-urls": { 335 | "version": "1.1.0", 336 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", 337 | "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", 338 | "dev": true, 339 | "requires": { 340 | "abab": "^2.0.0", 341 | "whatwg-mimetype": "^2.2.0", 342 | "whatwg-url": "^7.0.0" 343 | } 344 | }, 345 | "debug": { 346 | "version": "4.1.1", 347 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 348 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 349 | "requires": { 350 | "ms": "^2.1.1" 351 | }, 352 | "dependencies": { 353 | "ms": { 354 | "version": "2.1.1", 355 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 356 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 357 | } 358 | } 359 | }, 360 | "deep-eql": { 361 | "version": "3.0.1", 362 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 363 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 364 | "dev": true, 365 | "requires": { 366 | "type-detect": "^4.0.0" 367 | } 368 | }, 369 | "deep-is": { 370 | "version": "0.1.3", 371 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 372 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 373 | "dev": true 374 | }, 375 | "delayed-stream": { 376 | "version": "1.0.0", 377 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 378 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 379 | "dev": true 380 | }, 381 | "diff": { 382 | "version": "3.5.0", 383 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 384 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 385 | "dev": true 386 | }, 387 | "domexception": { 388 | "version": "1.0.1", 389 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", 390 | "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", 391 | "dev": true, 392 | "requires": { 393 | "webidl-conversions": "^4.0.2" 394 | } 395 | }, 396 | "ecc-jsbn": { 397 | "version": "0.1.2", 398 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 399 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 400 | "dev": true, 401 | "requires": { 402 | "jsbn": "~0.1.0", 403 | "safer-buffer": "^2.1.0" 404 | } 405 | }, 406 | "escape-string-regexp": { 407 | "version": "1.0.5", 408 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 409 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 410 | }, 411 | "escodegen": { 412 | "version": "1.11.0", 413 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", 414 | "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", 415 | "dev": true, 416 | "requires": { 417 | "esprima": "^3.1.3", 418 | "estraverse": "^4.2.0", 419 | "esutils": "^2.0.2", 420 | "optionator": "^0.8.1", 421 | "source-map": "~0.6.1" 422 | }, 423 | "dependencies": { 424 | "source-map": { 425 | "version": "0.6.1", 426 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 427 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 428 | "dev": true, 429 | "optional": true 430 | } 431 | } 432 | }, 433 | "esprima": { 434 | "version": "3.1.3", 435 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", 436 | "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", 437 | "dev": true 438 | }, 439 | "estraverse": { 440 | "version": "4.2.0", 441 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 442 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 443 | "dev": true 444 | }, 445 | "esutils": { 446 | "version": "2.0.2", 447 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 448 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" 449 | }, 450 | "extend": { 451 | "version": "3.0.2", 452 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 453 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 454 | "dev": true 455 | }, 456 | "extsprintf": { 457 | "version": "1.3.0", 458 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 459 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", 460 | "dev": true 461 | }, 462 | "fast-deep-equal": { 463 | "version": "2.0.1", 464 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 465 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 466 | "dev": true 467 | }, 468 | "fast-json-stable-stringify": { 469 | "version": "2.0.0", 470 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 471 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 472 | "dev": true 473 | }, 474 | "fast-levenshtein": { 475 | "version": "2.0.6", 476 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 477 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 478 | "dev": true 479 | }, 480 | "forever-agent": { 481 | "version": "0.6.1", 482 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 483 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", 484 | "dev": true 485 | }, 486 | "form-data": { 487 | "version": "2.3.3", 488 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 489 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 490 | "dev": true, 491 | "requires": { 492 | "asynckit": "^0.4.0", 493 | "combined-stream": "^1.0.6", 494 | "mime-types": "^2.1.12" 495 | } 496 | }, 497 | "fs.realpath": { 498 | "version": "1.0.0", 499 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 500 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 501 | "dev": true 502 | }, 503 | "get-func-name": { 504 | "version": "2.0.0", 505 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 506 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 507 | "dev": true 508 | }, 509 | "getpass": { 510 | "version": "0.1.7", 511 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 512 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 513 | "dev": true, 514 | "requires": { 515 | "assert-plus": "^1.0.0" 516 | } 517 | }, 518 | "glob": { 519 | "version": "7.1.2", 520 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 521 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 522 | "dev": true, 523 | "requires": { 524 | "fs.realpath": "^1.0.0", 525 | "inflight": "^1.0.4", 526 | "inherits": "2", 527 | "minimatch": "^3.0.4", 528 | "once": "^1.3.0", 529 | "path-is-absolute": "^1.0.0" 530 | } 531 | }, 532 | "globals": { 533 | "version": "11.11.0", 534 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", 535 | "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" 536 | }, 537 | "growl": { 538 | "version": "1.10.5", 539 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 540 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 541 | "dev": true 542 | }, 543 | "har-schema": { 544 | "version": "2.0.0", 545 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 546 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", 547 | "dev": true 548 | }, 549 | "har-validator": { 550 | "version": "5.1.3", 551 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 552 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 553 | "dev": true, 554 | "requires": { 555 | "ajv": "^6.5.5", 556 | "har-schema": "^2.0.0" 557 | } 558 | }, 559 | "has-flag": { 560 | "version": "3.0.0", 561 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 562 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 563 | }, 564 | "he": { 565 | "version": "1.1.1", 566 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 567 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 568 | "dev": true 569 | }, 570 | "html-encoding-sniffer": { 571 | "version": "1.0.2", 572 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", 573 | "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", 574 | "dev": true, 575 | "requires": { 576 | "whatwg-encoding": "^1.0.1" 577 | } 578 | }, 579 | "http-signature": { 580 | "version": "1.2.0", 581 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 582 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 583 | "dev": true, 584 | "requires": { 585 | "assert-plus": "^1.0.0", 586 | "jsprim": "^1.2.2", 587 | "sshpk": "^1.7.0" 588 | } 589 | }, 590 | "iconv-lite": { 591 | "version": "0.4.24", 592 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 593 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 594 | "dev": true, 595 | "requires": { 596 | "safer-buffer": ">= 2.1.2 < 3" 597 | } 598 | }, 599 | "inflight": { 600 | "version": "1.0.6", 601 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 602 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 603 | "dev": true, 604 | "requires": { 605 | "once": "^1.3.0", 606 | "wrappy": "1" 607 | } 608 | }, 609 | "inherits": { 610 | "version": "2.0.3", 611 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 612 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 613 | "dev": true 614 | }, 615 | "is-typedarray": { 616 | "version": "1.0.0", 617 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 618 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", 619 | "dev": true 620 | }, 621 | "isstream": { 622 | "version": "0.1.2", 623 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 624 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", 625 | "dev": true 626 | }, 627 | "istanbul-lib-coverage": { 628 | "version": "2.0.3", 629 | "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", 630 | "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==" 631 | }, 632 | "istanbul-lib-instrument": { 633 | "version": "3.1.0", 634 | "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", 635 | "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", 636 | "requires": { 637 | "@babel/generator": "^7.0.0", 638 | "@babel/parser": "^7.0.0", 639 | "@babel/template": "^7.0.0", 640 | "@babel/traverse": "^7.0.0", 641 | "@babel/types": "^7.0.0", 642 | "istanbul-lib-coverage": "^2.0.3", 643 | "semver": "^5.5.0" 644 | } 645 | }, 646 | "jquery": { 647 | "version": "3.4.1", 648 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", 649 | "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==", 650 | "dev": true 651 | }, 652 | "js-tokens": { 653 | "version": "4.0.0", 654 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 655 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 656 | }, 657 | "jsbn": { 658 | "version": "0.1.1", 659 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 660 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 661 | "dev": true 662 | }, 663 | "jsdom": { 664 | "version": "13.2.0", 665 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-13.2.0.tgz", 666 | "integrity": "sha1-saDb2twlVDUmK+jqNyPS26DX6zo=", 667 | "dev": true, 668 | "requires": { 669 | "abab": "^2.0.0", 670 | "acorn": "^6.0.4", 671 | "acorn-globals": "^4.3.0", 672 | "array-equal": "^1.0.0", 673 | "cssom": "^0.3.4", 674 | "cssstyle": "^1.1.1", 675 | "data-urls": "^1.1.0", 676 | "domexception": "^1.0.1", 677 | "escodegen": "^1.11.0", 678 | "html-encoding-sniffer": "^1.0.2", 679 | "nwsapi": "^2.0.9", 680 | "parse5": "5.1.0", 681 | "pn": "^1.1.0", 682 | "request": "^2.88.0", 683 | "request-promise-native": "^1.0.5", 684 | "saxes": "^3.1.5", 685 | "symbol-tree": "^3.2.2", 686 | "tough-cookie": "^2.5.0", 687 | "w3c-hr-time": "^1.0.1", 688 | "w3c-xmlserializer": "^1.0.1", 689 | "webidl-conversions": "^4.0.2", 690 | "whatwg-encoding": "^1.0.5", 691 | "whatwg-mimetype": "^2.3.0", 692 | "whatwg-url": "^7.0.0", 693 | "ws": "^6.1.2", 694 | "xml-name-validator": "^3.0.0" 695 | } 696 | }, 697 | "jsesc": { 698 | "version": "2.5.2", 699 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 700 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" 701 | }, 702 | "json-schema": { 703 | "version": "0.2.3", 704 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 705 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", 706 | "dev": true 707 | }, 708 | "json-schema-traverse": { 709 | "version": "0.4.1", 710 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 711 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 712 | "dev": true 713 | }, 714 | "json-stringify-safe": { 715 | "version": "5.0.1", 716 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 717 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 718 | "dev": true 719 | }, 720 | "jsprim": { 721 | "version": "1.4.1", 722 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 723 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 724 | "dev": true, 725 | "requires": { 726 | "assert-plus": "1.0.0", 727 | "extsprintf": "1.3.0", 728 | "json-schema": "0.2.3", 729 | "verror": "1.10.0" 730 | } 731 | }, 732 | "levn": { 733 | "version": "0.3.0", 734 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 735 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 736 | "dev": true, 737 | "requires": { 738 | "prelude-ls": "~1.1.2", 739 | "type-check": "~0.3.2" 740 | } 741 | }, 742 | "lodash": { 743 | "version": "4.17.11", 744 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 745 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" 746 | }, 747 | "lodash.sortby": { 748 | "version": "4.7.0", 749 | "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", 750 | "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", 751 | "dev": true 752 | }, 753 | "mime-db": { 754 | "version": "1.38.0", 755 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", 756 | "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", 757 | "dev": true 758 | }, 759 | "mime-types": { 760 | "version": "2.1.22", 761 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", 762 | "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", 763 | "dev": true, 764 | "requires": { 765 | "mime-db": "~1.38.0" 766 | } 767 | }, 768 | "minimatch": { 769 | "version": "3.0.4", 770 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 771 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 772 | "dev": true, 773 | "requires": { 774 | "brace-expansion": "^1.1.7" 775 | } 776 | }, 777 | "mkdirp": { 778 | "version": "0.5.1", 779 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 780 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 781 | "dev": true, 782 | "requires": { 783 | "minimist": "0.0.8" 784 | }, 785 | "dependencies": { 786 | "minimist": { 787 | "version": "0.0.8", 788 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 789 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 790 | "dev": true 791 | } 792 | } 793 | }, 794 | "mocha": { 795 | "version": "5.2.0", 796 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 797 | "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", 798 | "dev": true, 799 | "requires": { 800 | "browser-stdout": "1.3.1", 801 | "commander": "2.15.1", 802 | "debug": "3.1.0", 803 | "diff": "3.5.0", 804 | "escape-string-regexp": "1.0.5", 805 | "glob": "7.1.2", 806 | "growl": "1.10.5", 807 | "he": "1.1.1", 808 | "minimatch": "3.0.4", 809 | "mkdirp": "0.5.1", 810 | "supports-color": "5.4.0" 811 | }, 812 | "dependencies": { 813 | "commander": { 814 | "version": "2.15.1", 815 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 816 | "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=", 817 | "dev": true 818 | }, 819 | "debug": { 820 | "version": "3.1.0", 821 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 822 | "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", 823 | "dev": true, 824 | "requires": { 825 | "ms": "2.0.0" 826 | } 827 | } 828 | } 829 | }, 830 | "ms": { 831 | "version": "2.0.0", 832 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 833 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 834 | "dev": true 835 | }, 836 | "nwsapi": { 837 | "version": "2.1.0", 838 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", 839 | "integrity": "sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==", 840 | "dev": true 841 | }, 842 | "nyc": { 843 | "version": "13.3.0", 844 | "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", 845 | "integrity": "sha1-2k2+kanIuerT9PM0THbzU+PHjHU=", 846 | "requires": { 847 | "archy": "^1.0.0", 848 | "arrify": "^1.0.1", 849 | "caching-transform": "^3.0.1", 850 | "convert-source-map": "^1.6.0", 851 | "find-cache-dir": "^2.0.0", 852 | "find-up": "^3.0.0", 853 | "foreground-child": "^1.5.6", 854 | "glob": "^7.1.3", 855 | "istanbul-lib-coverage": "^2.0.3", 856 | "istanbul-lib-hook": "^2.0.3", 857 | "istanbul-lib-instrument": "^3.1.0", 858 | "istanbul-lib-report": "^2.0.4", 859 | "istanbul-lib-source-maps": "^3.0.2", 860 | "istanbul-reports": "^2.1.1", 861 | "make-dir": "^1.3.0", 862 | "merge-source-map": "^1.1.0", 863 | "resolve-from": "^4.0.0", 864 | "rimraf": "^2.6.3", 865 | "signal-exit": "^3.0.2", 866 | "spawn-wrap": "^1.4.2", 867 | "test-exclude": "^5.1.0", 868 | "uuid": "^3.3.2", 869 | "yargs": "^12.0.5", 870 | "yargs-parser": "^11.1.1" 871 | }, 872 | "dependencies": { 873 | "ansi-regex": { 874 | "version": "3.0.0", 875 | "bundled": true 876 | }, 877 | "append-transform": { 878 | "version": "1.0.0", 879 | "bundled": true, 880 | "requires": { 881 | "default-require-extensions": "^2.0.0" 882 | } 883 | }, 884 | "archy": { 885 | "version": "1.0.0", 886 | "bundled": true 887 | }, 888 | "arrify": { 889 | "version": "1.0.1", 890 | "bundled": true 891 | }, 892 | "async": { 893 | "version": "2.6.2", 894 | "bundled": true, 895 | "requires": { 896 | "lodash": "^4.17.11" 897 | } 898 | }, 899 | "balanced-match": { 900 | "version": "1.0.0", 901 | "bundled": true 902 | }, 903 | "brace-expansion": { 904 | "version": "1.1.11", 905 | "bundled": true, 906 | "requires": { 907 | "balanced-match": "^1.0.0", 908 | "concat-map": "0.0.1" 909 | } 910 | }, 911 | "caching-transform": { 912 | "version": "3.0.1", 913 | "bundled": true, 914 | "requires": { 915 | "hasha": "^3.0.0", 916 | "make-dir": "^1.3.0", 917 | "package-hash": "^3.0.0", 918 | "write-file-atomic": "^2.3.0" 919 | } 920 | }, 921 | "camelcase": { 922 | "version": "5.0.0", 923 | "bundled": true 924 | }, 925 | "cliui": { 926 | "version": "4.1.0", 927 | "bundled": true, 928 | "requires": { 929 | "string-width": "^2.1.1", 930 | "strip-ansi": "^4.0.0", 931 | "wrap-ansi": "^2.0.0" 932 | } 933 | }, 934 | "code-point-at": { 935 | "version": "1.1.0", 936 | "bundled": true 937 | }, 938 | "commander": { 939 | "version": "2.17.1", 940 | "bundled": true, 941 | "optional": true 942 | }, 943 | "commondir": { 944 | "version": "1.0.1", 945 | "bundled": true 946 | }, 947 | "concat-map": { 948 | "version": "0.0.1", 949 | "bundled": true 950 | }, 951 | "convert-source-map": { 952 | "version": "1.6.0", 953 | "bundled": true, 954 | "requires": { 955 | "safe-buffer": "~5.1.1" 956 | } 957 | }, 958 | "cross-spawn": { 959 | "version": "4.0.2", 960 | "bundled": true, 961 | "requires": { 962 | "lru-cache": "^4.0.1", 963 | "which": "^1.2.9" 964 | } 965 | }, 966 | "debug": { 967 | "version": "4.1.1", 968 | "bundled": true, 969 | "requires": { 970 | "ms": "^2.1.1" 971 | } 972 | }, 973 | "decamelize": { 974 | "version": "1.2.0", 975 | "bundled": true 976 | }, 977 | "default-require-extensions": { 978 | "version": "2.0.0", 979 | "bundled": true, 980 | "requires": { 981 | "strip-bom": "^3.0.0" 982 | } 983 | }, 984 | "end-of-stream": { 985 | "version": "1.4.1", 986 | "bundled": true, 987 | "requires": { 988 | "once": "^1.4.0" 989 | } 990 | }, 991 | "error-ex": { 992 | "version": "1.3.2", 993 | "bundled": true, 994 | "requires": { 995 | "is-arrayish": "^0.2.1" 996 | } 997 | }, 998 | "es6-error": { 999 | "version": "4.1.1", 1000 | "bundled": true 1001 | }, 1002 | "execa": { 1003 | "version": "1.0.0", 1004 | "bundled": true, 1005 | "requires": { 1006 | "cross-spawn": "^6.0.0", 1007 | "get-stream": "^4.0.0", 1008 | "is-stream": "^1.1.0", 1009 | "npm-run-path": "^2.0.0", 1010 | "p-finally": "^1.0.0", 1011 | "signal-exit": "^3.0.0", 1012 | "strip-eof": "^1.0.0" 1013 | }, 1014 | "dependencies": { 1015 | "cross-spawn": { 1016 | "version": "6.0.5", 1017 | "bundled": true, 1018 | "requires": { 1019 | "nice-try": "^1.0.4", 1020 | "path-key": "^2.0.1", 1021 | "semver": "^5.5.0", 1022 | "shebang-command": "^1.2.0", 1023 | "which": "^1.2.9" 1024 | } 1025 | } 1026 | } 1027 | }, 1028 | "find-cache-dir": { 1029 | "version": "2.0.0", 1030 | "bundled": true, 1031 | "requires": { 1032 | "commondir": "^1.0.1", 1033 | "make-dir": "^1.0.0", 1034 | "pkg-dir": "^3.0.0" 1035 | } 1036 | }, 1037 | "find-up": { 1038 | "version": "3.0.0", 1039 | "bundled": true, 1040 | "requires": { 1041 | "locate-path": "^3.0.0" 1042 | } 1043 | }, 1044 | "foreground-child": { 1045 | "version": "1.5.6", 1046 | "bundled": true, 1047 | "requires": { 1048 | "cross-spawn": "^4", 1049 | "signal-exit": "^3.0.0" 1050 | } 1051 | }, 1052 | "fs.realpath": { 1053 | "version": "1.0.0", 1054 | "bundled": true 1055 | }, 1056 | "get-caller-file": { 1057 | "version": "1.0.3", 1058 | "bundled": true 1059 | }, 1060 | "get-stream": { 1061 | "version": "4.1.0", 1062 | "bundled": true, 1063 | "requires": { 1064 | "pump": "^3.0.0" 1065 | } 1066 | }, 1067 | "glob": { 1068 | "version": "7.1.3", 1069 | "bundled": true, 1070 | "requires": { 1071 | "fs.realpath": "^1.0.0", 1072 | "inflight": "^1.0.4", 1073 | "inherits": "2", 1074 | "minimatch": "^3.0.4", 1075 | "once": "^1.3.0", 1076 | "path-is-absolute": "^1.0.0" 1077 | } 1078 | }, 1079 | "graceful-fs": { 1080 | "version": "4.1.15", 1081 | "bundled": true 1082 | }, 1083 | "handlebars": { 1084 | "version": "4.1.0", 1085 | "bundled": true, 1086 | "requires": { 1087 | "async": "^2.5.0", 1088 | "optimist": "^0.6.1", 1089 | "source-map": "^0.6.1", 1090 | "uglify-js": "^3.1.4" 1091 | }, 1092 | "dependencies": { 1093 | "source-map": { 1094 | "version": "0.6.1", 1095 | "bundled": true 1096 | } 1097 | } 1098 | }, 1099 | "has-flag": { 1100 | "version": "3.0.0", 1101 | "bundled": true 1102 | }, 1103 | "hasha": { 1104 | "version": "3.0.0", 1105 | "bundled": true, 1106 | "requires": { 1107 | "is-stream": "^1.0.1" 1108 | } 1109 | }, 1110 | "hosted-git-info": { 1111 | "version": "2.7.1", 1112 | "bundled": true 1113 | }, 1114 | "imurmurhash": { 1115 | "version": "0.1.4", 1116 | "bundled": true 1117 | }, 1118 | "inflight": { 1119 | "version": "1.0.6", 1120 | "bundled": true, 1121 | "requires": { 1122 | "once": "^1.3.0", 1123 | "wrappy": "1" 1124 | } 1125 | }, 1126 | "inherits": { 1127 | "version": "2.0.3", 1128 | "bundled": true 1129 | }, 1130 | "invert-kv": { 1131 | "version": "2.0.0", 1132 | "bundled": true 1133 | }, 1134 | "is-arrayish": { 1135 | "version": "0.2.1", 1136 | "bundled": true 1137 | }, 1138 | "is-fullwidth-code-point": { 1139 | "version": "2.0.0", 1140 | "bundled": true 1141 | }, 1142 | "is-stream": { 1143 | "version": "1.1.0", 1144 | "bundled": true 1145 | }, 1146 | "isexe": { 1147 | "version": "2.0.0", 1148 | "bundled": true 1149 | }, 1150 | "istanbul-lib-coverage": { 1151 | "version": "2.0.3", 1152 | "bundled": true 1153 | }, 1154 | "istanbul-lib-hook": { 1155 | "version": "2.0.3", 1156 | "bundled": true, 1157 | "requires": { 1158 | "append-transform": "^1.0.0" 1159 | } 1160 | }, 1161 | "istanbul-lib-report": { 1162 | "version": "2.0.4", 1163 | "bundled": true, 1164 | "requires": { 1165 | "istanbul-lib-coverage": "^2.0.3", 1166 | "make-dir": "^1.3.0", 1167 | "supports-color": "^6.0.0" 1168 | }, 1169 | "dependencies": { 1170 | "supports-color": { 1171 | "version": "6.1.0", 1172 | "bundled": true, 1173 | "requires": { 1174 | "has-flag": "^3.0.0" 1175 | } 1176 | } 1177 | } 1178 | }, 1179 | "istanbul-lib-source-maps": { 1180 | "version": "3.0.2", 1181 | "bundled": true, 1182 | "requires": { 1183 | "debug": "^4.1.1", 1184 | "istanbul-lib-coverage": "^2.0.3", 1185 | "make-dir": "^1.3.0", 1186 | "rimraf": "^2.6.2", 1187 | "source-map": "^0.6.1" 1188 | }, 1189 | "dependencies": { 1190 | "source-map": { 1191 | "version": "0.6.1", 1192 | "bundled": true 1193 | } 1194 | } 1195 | }, 1196 | "istanbul-reports": { 1197 | "version": "2.1.1", 1198 | "bundled": true, 1199 | "requires": { 1200 | "handlebars": "^4.1.0" 1201 | } 1202 | }, 1203 | "json-parse-better-errors": { 1204 | "version": "1.0.2", 1205 | "bundled": true 1206 | }, 1207 | "lcid": { 1208 | "version": "2.0.0", 1209 | "bundled": true, 1210 | "requires": { 1211 | "invert-kv": "^2.0.0" 1212 | } 1213 | }, 1214 | "load-json-file": { 1215 | "version": "4.0.0", 1216 | "bundled": true, 1217 | "requires": { 1218 | "graceful-fs": "^4.1.2", 1219 | "parse-json": "^4.0.0", 1220 | "pify": "^3.0.0", 1221 | "strip-bom": "^3.0.0" 1222 | } 1223 | }, 1224 | "locate-path": { 1225 | "version": "3.0.0", 1226 | "bundled": true, 1227 | "requires": { 1228 | "p-locate": "^3.0.0", 1229 | "path-exists": "^3.0.0" 1230 | } 1231 | }, 1232 | "lodash": { 1233 | "version": "4.17.11", 1234 | "bundled": true 1235 | }, 1236 | "lodash.flattendeep": { 1237 | "version": "4.4.0", 1238 | "bundled": true 1239 | }, 1240 | "lru-cache": { 1241 | "version": "4.1.5", 1242 | "bundled": true, 1243 | "requires": { 1244 | "pseudomap": "^1.0.2", 1245 | "yallist": "^2.1.2" 1246 | } 1247 | }, 1248 | "make-dir": { 1249 | "version": "1.3.0", 1250 | "bundled": true, 1251 | "requires": { 1252 | "pify": "^3.0.0" 1253 | } 1254 | }, 1255 | "map-age-cleaner": { 1256 | "version": "0.1.3", 1257 | "bundled": true, 1258 | "requires": { 1259 | "p-defer": "^1.0.0" 1260 | } 1261 | }, 1262 | "mem": { 1263 | "version": "4.1.0", 1264 | "bundled": true, 1265 | "requires": { 1266 | "map-age-cleaner": "^0.1.1", 1267 | "mimic-fn": "^1.0.0", 1268 | "p-is-promise": "^2.0.0" 1269 | } 1270 | }, 1271 | "merge-source-map": { 1272 | "version": "1.1.0", 1273 | "bundled": true, 1274 | "requires": { 1275 | "source-map": "^0.6.1" 1276 | }, 1277 | "dependencies": { 1278 | "source-map": { 1279 | "version": "0.6.1", 1280 | "bundled": true 1281 | } 1282 | } 1283 | }, 1284 | "mimic-fn": { 1285 | "version": "1.2.0", 1286 | "bundled": true 1287 | }, 1288 | "minimatch": { 1289 | "version": "3.0.4", 1290 | "bundled": true, 1291 | "requires": { 1292 | "brace-expansion": "^1.1.7" 1293 | } 1294 | }, 1295 | "minimist": { 1296 | "version": "0.0.10", 1297 | "bundled": true 1298 | }, 1299 | "mkdirp": { 1300 | "version": "0.5.1", 1301 | "bundled": true, 1302 | "requires": { 1303 | "minimist": "0.0.8" 1304 | }, 1305 | "dependencies": { 1306 | "minimist": { 1307 | "version": "0.0.8", 1308 | "bundled": true 1309 | } 1310 | } 1311 | }, 1312 | "ms": { 1313 | "version": "2.1.1", 1314 | "bundled": true 1315 | }, 1316 | "nice-try": { 1317 | "version": "1.0.5", 1318 | "bundled": true 1319 | }, 1320 | "normalize-package-data": { 1321 | "version": "2.5.0", 1322 | "bundled": true, 1323 | "requires": { 1324 | "hosted-git-info": "^2.1.4", 1325 | "resolve": "^1.10.0", 1326 | "semver": "2 || 3 || 4 || 5", 1327 | "validate-npm-package-license": "^3.0.1" 1328 | } 1329 | }, 1330 | "npm-run-path": { 1331 | "version": "2.0.2", 1332 | "bundled": true, 1333 | "requires": { 1334 | "path-key": "^2.0.0" 1335 | } 1336 | }, 1337 | "number-is-nan": { 1338 | "version": "1.0.1", 1339 | "bundled": true 1340 | }, 1341 | "once": { 1342 | "version": "1.4.0", 1343 | "bundled": true, 1344 | "requires": { 1345 | "wrappy": "1" 1346 | } 1347 | }, 1348 | "optimist": { 1349 | "version": "0.6.1", 1350 | "bundled": true, 1351 | "requires": { 1352 | "minimist": "~0.0.1", 1353 | "wordwrap": "~0.0.2" 1354 | } 1355 | }, 1356 | "os-homedir": { 1357 | "version": "1.0.2", 1358 | "bundled": true 1359 | }, 1360 | "os-locale": { 1361 | "version": "3.1.0", 1362 | "bundled": true, 1363 | "requires": { 1364 | "execa": "^1.0.0", 1365 | "lcid": "^2.0.0", 1366 | "mem": "^4.0.0" 1367 | } 1368 | }, 1369 | "p-defer": { 1370 | "version": "1.0.0", 1371 | "bundled": true 1372 | }, 1373 | "p-finally": { 1374 | "version": "1.0.0", 1375 | "bundled": true 1376 | }, 1377 | "p-is-promise": { 1378 | "version": "2.0.0", 1379 | "bundled": true 1380 | }, 1381 | "p-limit": { 1382 | "version": "2.1.0", 1383 | "bundled": true, 1384 | "requires": { 1385 | "p-try": "^2.0.0" 1386 | } 1387 | }, 1388 | "p-locate": { 1389 | "version": "3.0.0", 1390 | "bundled": true, 1391 | "requires": { 1392 | "p-limit": "^2.0.0" 1393 | } 1394 | }, 1395 | "p-try": { 1396 | "version": "2.0.0", 1397 | "bundled": true 1398 | }, 1399 | "package-hash": { 1400 | "version": "3.0.0", 1401 | "bundled": true, 1402 | "requires": { 1403 | "graceful-fs": "^4.1.15", 1404 | "hasha": "^3.0.0", 1405 | "lodash.flattendeep": "^4.4.0", 1406 | "release-zalgo": "^1.0.0" 1407 | } 1408 | }, 1409 | "parse-json": { 1410 | "version": "4.0.0", 1411 | "bundled": true, 1412 | "requires": { 1413 | "error-ex": "^1.3.1", 1414 | "json-parse-better-errors": "^1.0.1" 1415 | } 1416 | }, 1417 | "path-exists": { 1418 | "version": "3.0.0", 1419 | "bundled": true 1420 | }, 1421 | "path-is-absolute": { 1422 | "version": "1.0.1", 1423 | "bundled": true 1424 | }, 1425 | "path-key": { 1426 | "version": "2.0.1", 1427 | "bundled": true 1428 | }, 1429 | "path-parse": { 1430 | "version": "1.0.6", 1431 | "bundled": true 1432 | }, 1433 | "path-type": { 1434 | "version": "3.0.0", 1435 | "bundled": true, 1436 | "requires": { 1437 | "pify": "^3.0.0" 1438 | } 1439 | }, 1440 | "pify": { 1441 | "version": "3.0.0", 1442 | "bundled": true 1443 | }, 1444 | "pkg-dir": { 1445 | "version": "3.0.0", 1446 | "bundled": true, 1447 | "requires": { 1448 | "find-up": "^3.0.0" 1449 | } 1450 | }, 1451 | "pseudomap": { 1452 | "version": "1.0.2", 1453 | "bundled": true 1454 | }, 1455 | "pump": { 1456 | "version": "3.0.0", 1457 | "bundled": true, 1458 | "requires": { 1459 | "end-of-stream": "^1.1.0", 1460 | "once": "^1.3.1" 1461 | } 1462 | }, 1463 | "read-pkg": { 1464 | "version": "3.0.0", 1465 | "bundled": true, 1466 | "requires": { 1467 | "load-json-file": "^4.0.0", 1468 | "normalize-package-data": "^2.3.2", 1469 | "path-type": "^3.0.0" 1470 | } 1471 | }, 1472 | "read-pkg-up": { 1473 | "version": "4.0.0", 1474 | "bundled": true, 1475 | "requires": { 1476 | "find-up": "^3.0.0", 1477 | "read-pkg": "^3.0.0" 1478 | } 1479 | }, 1480 | "release-zalgo": { 1481 | "version": "1.0.0", 1482 | "bundled": true, 1483 | "requires": { 1484 | "es6-error": "^4.0.1" 1485 | } 1486 | }, 1487 | "require-directory": { 1488 | "version": "2.1.1", 1489 | "bundled": true 1490 | }, 1491 | "require-main-filename": { 1492 | "version": "1.0.1", 1493 | "bundled": true 1494 | }, 1495 | "resolve": { 1496 | "version": "1.10.0", 1497 | "bundled": true, 1498 | "requires": { 1499 | "path-parse": "^1.0.6" 1500 | } 1501 | }, 1502 | "resolve-from": { 1503 | "version": "4.0.0", 1504 | "bundled": true 1505 | }, 1506 | "rimraf": { 1507 | "version": "2.6.3", 1508 | "bundled": true, 1509 | "requires": { 1510 | "glob": "^7.1.3" 1511 | } 1512 | }, 1513 | "safe-buffer": { 1514 | "version": "5.1.2", 1515 | "bundled": true 1516 | }, 1517 | "semver": { 1518 | "version": "5.6.0", 1519 | "bundled": true 1520 | }, 1521 | "set-blocking": { 1522 | "version": "2.0.0", 1523 | "bundled": true 1524 | }, 1525 | "shebang-command": { 1526 | "version": "1.2.0", 1527 | "bundled": true, 1528 | "requires": { 1529 | "shebang-regex": "^1.0.0" 1530 | } 1531 | }, 1532 | "shebang-regex": { 1533 | "version": "1.0.0", 1534 | "bundled": true 1535 | }, 1536 | "signal-exit": { 1537 | "version": "3.0.2", 1538 | "bundled": true 1539 | }, 1540 | "spawn-wrap": { 1541 | "version": "1.4.2", 1542 | "bundled": true, 1543 | "requires": { 1544 | "foreground-child": "^1.5.6", 1545 | "mkdirp": "^0.5.0", 1546 | "os-homedir": "^1.0.1", 1547 | "rimraf": "^2.6.2", 1548 | "signal-exit": "^3.0.2", 1549 | "which": "^1.3.0" 1550 | } 1551 | }, 1552 | "spdx-correct": { 1553 | "version": "3.1.0", 1554 | "bundled": true, 1555 | "requires": { 1556 | "spdx-expression-parse": "^3.0.0", 1557 | "spdx-license-ids": "^3.0.0" 1558 | } 1559 | }, 1560 | "spdx-exceptions": { 1561 | "version": "2.2.0", 1562 | "bundled": true 1563 | }, 1564 | "spdx-expression-parse": { 1565 | "version": "3.0.0", 1566 | "bundled": true, 1567 | "requires": { 1568 | "spdx-exceptions": "^2.1.0", 1569 | "spdx-license-ids": "^3.0.0" 1570 | } 1571 | }, 1572 | "spdx-license-ids": { 1573 | "version": "3.0.3", 1574 | "bundled": true 1575 | }, 1576 | "string-width": { 1577 | "version": "2.1.1", 1578 | "bundled": true, 1579 | "requires": { 1580 | "is-fullwidth-code-point": "^2.0.0", 1581 | "strip-ansi": "^4.0.0" 1582 | } 1583 | }, 1584 | "strip-ansi": { 1585 | "version": "4.0.0", 1586 | "bundled": true, 1587 | "requires": { 1588 | "ansi-regex": "^3.0.0" 1589 | } 1590 | }, 1591 | "strip-bom": { 1592 | "version": "3.0.0", 1593 | "bundled": true 1594 | }, 1595 | "strip-eof": { 1596 | "version": "1.0.0", 1597 | "bundled": true 1598 | }, 1599 | "test-exclude": { 1600 | "version": "5.1.0", 1601 | "bundled": true, 1602 | "requires": { 1603 | "arrify": "^1.0.1", 1604 | "minimatch": "^3.0.4", 1605 | "read-pkg-up": "^4.0.0", 1606 | "require-main-filename": "^1.0.1" 1607 | } 1608 | }, 1609 | "uglify-js": { 1610 | "version": "3.4.9", 1611 | "bundled": true, 1612 | "optional": true, 1613 | "requires": { 1614 | "commander": "~2.17.1", 1615 | "source-map": "~0.6.1" 1616 | }, 1617 | "dependencies": { 1618 | "source-map": { 1619 | "version": "0.6.1", 1620 | "bundled": true, 1621 | "optional": true 1622 | } 1623 | } 1624 | }, 1625 | "uuid": { 1626 | "version": "3.3.2", 1627 | "bundled": true 1628 | }, 1629 | "validate-npm-package-license": { 1630 | "version": "3.0.4", 1631 | "bundled": true, 1632 | "requires": { 1633 | "spdx-correct": "^3.0.0", 1634 | "spdx-expression-parse": "^3.0.0" 1635 | } 1636 | }, 1637 | "which": { 1638 | "version": "1.3.1", 1639 | "bundled": true, 1640 | "requires": { 1641 | "isexe": "^2.0.0" 1642 | } 1643 | }, 1644 | "which-module": { 1645 | "version": "2.0.0", 1646 | "bundled": true 1647 | }, 1648 | "wordwrap": { 1649 | "version": "0.0.3", 1650 | "bundled": true 1651 | }, 1652 | "wrap-ansi": { 1653 | "version": "2.1.0", 1654 | "bundled": true, 1655 | "requires": { 1656 | "string-width": "^1.0.1", 1657 | "strip-ansi": "^3.0.1" 1658 | }, 1659 | "dependencies": { 1660 | "ansi-regex": { 1661 | "version": "2.1.1", 1662 | "bundled": true 1663 | }, 1664 | "is-fullwidth-code-point": { 1665 | "version": "1.0.0", 1666 | "bundled": true, 1667 | "requires": { 1668 | "number-is-nan": "^1.0.0" 1669 | } 1670 | }, 1671 | "string-width": { 1672 | "version": "1.0.2", 1673 | "bundled": true, 1674 | "requires": { 1675 | "code-point-at": "^1.0.0", 1676 | "is-fullwidth-code-point": "^1.0.0", 1677 | "strip-ansi": "^3.0.0" 1678 | } 1679 | }, 1680 | "strip-ansi": { 1681 | "version": "3.0.1", 1682 | "bundled": true, 1683 | "requires": { 1684 | "ansi-regex": "^2.0.0" 1685 | } 1686 | } 1687 | } 1688 | }, 1689 | "wrappy": { 1690 | "version": "1.0.2", 1691 | "bundled": true 1692 | }, 1693 | "write-file-atomic": { 1694 | "version": "2.4.2", 1695 | "bundled": true, 1696 | "requires": { 1697 | "graceful-fs": "^4.1.11", 1698 | "imurmurhash": "^0.1.4", 1699 | "signal-exit": "^3.0.2" 1700 | } 1701 | }, 1702 | "y18n": { 1703 | "version": "4.0.0", 1704 | "bundled": true 1705 | }, 1706 | "yallist": { 1707 | "version": "2.1.2", 1708 | "bundled": true 1709 | }, 1710 | "yargs": { 1711 | "version": "12.0.5", 1712 | "bundled": true, 1713 | "requires": { 1714 | "cliui": "^4.0.0", 1715 | "decamelize": "^1.2.0", 1716 | "find-up": "^3.0.0", 1717 | "get-caller-file": "^1.0.1", 1718 | "os-locale": "^3.0.0", 1719 | "require-directory": "^2.1.1", 1720 | "require-main-filename": "^1.0.1", 1721 | "set-blocking": "^2.0.0", 1722 | "string-width": "^2.0.0", 1723 | "which-module": "^2.0.0", 1724 | "y18n": "^3.2.1 || ^4.0.0", 1725 | "yargs-parser": "^11.1.1" 1726 | } 1727 | }, 1728 | "yargs-parser": { 1729 | "version": "11.1.1", 1730 | "bundled": true, 1731 | "requires": { 1732 | "camelcase": "^5.0.0", 1733 | "decamelize": "^1.2.0" 1734 | } 1735 | } 1736 | } 1737 | }, 1738 | "oauth-sign": { 1739 | "version": "0.9.0", 1740 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 1741 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", 1742 | "dev": true 1743 | }, 1744 | "once": { 1745 | "version": "1.4.0", 1746 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1747 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1748 | "dev": true, 1749 | "requires": { 1750 | "wrappy": "1" 1751 | } 1752 | }, 1753 | "optionator": { 1754 | "version": "0.8.2", 1755 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 1756 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 1757 | "dev": true, 1758 | "requires": { 1759 | "deep-is": "~0.1.3", 1760 | "fast-levenshtein": "~2.0.4", 1761 | "levn": "~0.3.0", 1762 | "prelude-ls": "~1.1.2", 1763 | "type-check": "~0.3.2", 1764 | "wordwrap": "~1.0.0" 1765 | } 1766 | }, 1767 | "parse5": { 1768 | "version": "5.1.0", 1769 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", 1770 | "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", 1771 | "dev": true 1772 | }, 1773 | "path-is-absolute": { 1774 | "version": "1.0.1", 1775 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1776 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1777 | "dev": true 1778 | }, 1779 | "pathval": { 1780 | "version": "1.1.0", 1781 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1782 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1783 | "dev": true 1784 | }, 1785 | "performance-now": { 1786 | "version": "2.1.0", 1787 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1788 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", 1789 | "dev": true 1790 | }, 1791 | "pn": { 1792 | "version": "1.1.0", 1793 | "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", 1794 | "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", 1795 | "dev": true 1796 | }, 1797 | "prelude-ls": { 1798 | "version": "1.1.2", 1799 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1800 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1801 | "dev": true 1802 | }, 1803 | "psl": { 1804 | "version": "1.1.31", 1805 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", 1806 | "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", 1807 | "dev": true 1808 | }, 1809 | "punycode": { 1810 | "version": "2.1.1", 1811 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1812 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1813 | "dev": true 1814 | }, 1815 | "qs": { 1816 | "version": "6.5.2", 1817 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 1818 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", 1819 | "dev": true 1820 | }, 1821 | "request": { 1822 | "version": "2.88.0", 1823 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 1824 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 1825 | "dev": true, 1826 | "requires": { 1827 | "aws-sign2": "~0.7.0", 1828 | "aws4": "^1.8.0", 1829 | "caseless": "~0.12.0", 1830 | "combined-stream": "~1.0.6", 1831 | "extend": "~3.0.2", 1832 | "forever-agent": "~0.6.1", 1833 | "form-data": "~2.3.2", 1834 | "har-validator": "~5.1.0", 1835 | "http-signature": "~1.2.0", 1836 | "is-typedarray": "~1.0.0", 1837 | "isstream": "~0.1.2", 1838 | "json-stringify-safe": "~5.0.1", 1839 | "mime-types": "~2.1.19", 1840 | "oauth-sign": "~0.9.0", 1841 | "performance-now": "^2.1.0", 1842 | "qs": "~6.5.2", 1843 | "safe-buffer": "^5.1.2", 1844 | "tough-cookie": "~2.4.3", 1845 | "tunnel-agent": "^0.6.0", 1846 | "uuid": "^3.3.2" 1847 | }, 1848 | "dependencies": { 1849 | "punycode": { 1850 | "version": "1.4.1", 1851 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1852 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", 1853 | "dev": true 1854 | }, 1855 | "tough-cookie": { 1856 | "version": "2.4.3", 1857 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 1858 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 1859 | "dev": true, 1860 | "requires": { 1861 | "psl": "^1.1.24", 1862 | "punycode": "^1.4.1" 1863 | } 1864 | } 1865 | } 1866 | }, 1867 | "request-promise-core": { 1868 | "version": "1.1.2", 1869 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", 1870 | "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", 1871 | "dev": true, 1872 | "requires": { 1873 | "lodash": "^4.17.11" 1874 | } 1875 | }, 1876 | "request-promise-native": { 1877 | "version": "1.0.7", 1878 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", 1879 | "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", 1880 | "dev": true, 1881 | "requires": { 1882 | "request-promise-core": "1.1.2", 1883 | "stealthy-require": "^1.1.1", 1884 | "tough-cookie": "^2.3.3" 1885 | } 1886 | }, 1887 | "safe-buffer": { 1888 | "version": "5.1.2", 1889 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1890 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1891 | "dev": true 1892 | }, 1893 | "safer-buffer": { 1894 | "version": "2.1.2", 1895 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1896 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1897 | "dev": true 1898 | }, 1899 | "saxes": { 1900 | "version": "3.1.6", 1901 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.6.tgz", 1902 | "integrity": "sha512-LAYs+lChg1v5uKNzPtsgTxSS5hLo8aIhSMCJt1WMpefAxm3D1RTpMwSpb6ebdL31cubiLTnhokVktBW+cv9Y9w==", 1903 | "dev": true, 1904 | "requires": { 1905 | "xmlchars": "^1.3.1" 1906 | } 1907 | }, 1908 | "semver": { 1909 | "version": "5.6.0", 1910 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 1911 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" 1912 | }, 1913 | "source-map": { 1914 | "version": "0.5.7", 1915 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1916 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" 1917 | }, 1918 | "sshpk": { 1919 | "version": "1.16.1", 1920 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 1921 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 1922 | "dev": true, 1923 | "requires": { 1924 | "asn1": "~0.2.3", 1925 | "assert-plus": "^1.0.0", 1926 | "bcrypt-pbkdf": "^1.0.0", 1927 | "dashdash": "^1.12.0", 1928 | "ecc-jsbn": "~0.1.1", 1929 | "getpass": "^0.1.1", 1930 | "jsbn": "~0.1.0", 1931 | "safer-buffer": "^2.0.2", 1932 | "tweetnacl": "~0.14.0" 1933 | } 1934 | }, 1935 | "stealthy-require": { 1936 | "version": "1.1.1", 1937 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 1938 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", 1939 | "dev": true 1940 | }, 1941 | "supports-color": { 1942 | "version": "5.4.0", 1943 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 1944 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 1945 | "requires": { 1946 | "has-flag": "^3.0.0" 1947 | } 1948 | }, 1949 | "symbol-tree": { 1950 | "version": "3.2.2", 1951 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", 1952 | "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", 1953 | "dev": true 1954 | }, 1955 | "to-fast-properties": { 1956 | "version": "2.0.0", 1957 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1958 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" 1959 | }, 1960 | "tough-cookie": { 1961 | "version": "2.5.0", 1962 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1963 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1964 | "dev": true, 1965 | "requires": { 1966 | "psl": "^1.1.28", 1967 | "punycode": "^2.1.1" 1968 | } 1969 | }, 1970 | "tr46": { 1971 | "version": "1.0.1", 1972 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", 1973 | "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", 1974 | "dev": true, 1975 | "requires": { 1976 | "punycode": "^2.1.0" 1977 | } 1978 | }, 1979 | "trim-right": { 1980 | "version": "1.0.1", 1981 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", 1982 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" 1983 | }, 1984 | "tunnel-agent": { 1985 | "version": "0.6.0", 1986 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1987 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1988 | "dev": true, 1989 | "requires": { 1990 | "safe-buffer": "^5.0.1" 1991 | } 1992 | }, 1993 | "tweetnacl": { 1994 | "version": "0.14.5", 1995 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1996 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1997 | "dev": true 1998 | }, 1999 | "type-check": { 2000 | "version": "0.3.2", 2001 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 2002 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 2003 | "dev": true, 2004 | "requires": { 2005 | "prelude-ls": "~1.1.2" 2006 | } 2007 | }, 2008 | "type-detect": { 2009 | "version": "4.0.8", 2010 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 2011 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 2012 | "dev": true 2013 | }, 2014 | "uri-js": { 2015 | "version": "4.2.2", 2016 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 2017 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 2018 | "dev": true, 2019 | "requires": { 2020 | "punycode": "^2.1.0" 2021 | } 2022 | }, 2023 | "uuid": { 2024 | "version": "3.3.2", 2025 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 2026 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 2027 | "dev": true 2028 | }, 2029 | "verror": { 2030 | "version": "1.10.0", 2031 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 2032 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 2033 | "dev": true, 2034 | "requires": { 2035 | "assert-plus": "^1.0.0", 2036 | "core-util-is": "1.0.2", 2037 | "extsprintf": "^1.2.0" 2038 | } 2039 | }, 2040 | "w3c-hr-time": { 2041 | "version": "1.0.1", 2042 | "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", 2043 | "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", 2044 | "dev": true, 2045 | "requires": { 2046 | "browser-process-hrtime": "^0.1.2" 2047 | } 2048 | }, 2049 | "w3c-xmlserializer": { 2050 | "version": "1.0.1", 2051 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.0.1.tgz", 2052 | "integrity": "sha512-XZGI1OH/OLQr/NaJhhPmzhngwcAnZDLytsvXnRmlYeRkmbb0I7sqFFA22erq4WQR0sUu17ZSQOAV9mFwCqKRNg==", 2053 | "dev": true, 2054 | "requires": { 2055 | "domexception": "^1.0.1", 2056 | "webidl-conversions": "^4.0.2", 2057 | "xml-name-validator": "^3.0.0" 2058 | } 2059 | }, 2060 | "webidl-conversions": { 2061 | "version": "4.0.2", 2062 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", 2063 | "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", 2064 | "dev": true 2065 | }, 2066 | "whatwg-encoding": { 2067 | "version": "1.0.5", 2068 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", 2069 | "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", 2070 | "dev": true, 2071 | "requires": { 2072 | "iconv-lite": "0.4.24" 2073 | } 2074 | }, 2075 | "whatwg-mimetype": { 2076 | "version": "2.3.0", 2077 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", 2078 | "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", 2079 | "dev": true 2080 | }, 2081 | "whatwg-url": { 2082 | "version": "7.0.0", 2083 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", 2084 | "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", 2085 | "dev": true, 2086 | "requires": { 2087 | "lodash.sortby": "^4.7.0", 2088 | "tr46": "^1.0.1", 2089 | "webidl-conversions": "^4.0.2" 2090 | } 2091 | }, 2092 | "wordwrap": { 2093 | "version": "1.0.0", 2094 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 2095 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 2096 | "dev": true 2097 | }, 2098 | "wrappy": { 2099 | "version": "1.0.2", 2100 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2101 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 2102 | "dev": true 2103 | }, 2104 | "ws": { 2105 | "version": "6.1.3", 2106 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.3.tgz", 2107 | "integrity": "sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg==", 2108 | "dev": true, 2109 | "requires": { 2110 | "async-limiter": "~1.0.0" 2111 | } 2112 | }, 2113 | "xml-name-validator": { 2114 | "version": "3.0.0", 2115 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", 2116 | "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", 2117 | "dev": true 2118 | }, 2119 | "xmlchars": { 2120 | "version": "1.3.1", 2121 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-1.3.1.tgz", 2122 | "integrity": "sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==", 2123 | "dev": true 2124 | } 2125 | } 2126 | } 2127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upvotejs", 3 | "title": "UpvoteJS", 4 | "version": "2.1.0", 5 | "description": "Generate a voting widget like the one used on Stack Exchange sites.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/janosgyerik/upvotejs" 9 | }, 10 | "keywords": [ 11 | "upvote" 12 | ], 13 | "main": "./dist/upvotejs/upvotejs.vanilla.js", 14 | "style": "./dist/upvotejs/upvotejs.css", 15 | "files": [ 16 | "./dist/upvotejs/upvotejs.svg" 17 | ], 18 | "homepage": "https://janosgyerik.github.io/upvotejs", 19 | "author": { 20 | "name": "Janos Gyerik", 21 | "url": "https://twitter.com/janosgyerik" 22 | }, 23 | "contributors": [ 24 | { 25 | "name": "HappyZombies", 26 | "url": "https://github.com/HappyZombies" 27 | }, 28 | { 29 | "name": "Tomas Liubinas", 30 | "url": "https://github.com/tomasliubinas" 31 | }, 32 | { 33 | "name": "dilip-vishwa", 34 | "url": "https://github.com/dilip-vishwa" 35 | } 36 | ], 37 | "dependencies": { 38 | "nyc": "^13.3.0" 39 | }, 40 | "devDependencies": { 41 | "chai": "^4.2.0", 42 | "jquery": "^3.4.0", 43 | "jsdom": "^13.2.0", 44 | "mocha": "^5.2.0" 45 | }, 46 | "scripts": { 47 | "test": "nyc --reporter=html --report-dir=.coverage mocha" 48 | }, 49 | "license": "CC-BY-3.0" 50 | } 51 | -------------------------------------------------------------------------------- /tests/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests (jQuery) 5 | 6 | 7 | 12 | 13 | 14 |

Demo

15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/lib/common-tests.js: -------------------------------------------------------------------------------- 1 | const Tests = { 2 | run: (Setup, gen) => { 3 | const Utils = Setup.Utils; 4 | const assert = Setup.chai.assert; 5 | const uiTester = Setup.Utils.uiTester; 6 | const Upvote = Setup.Upvote; 7 | const $ = Setup.jQuery; 8 | 9 | const demo = params => { 10 | $('#demo').append(Utils.newDom(params)); 11 | Upvote.create(params.id); 12 | }; 13 | if (Setup.demo) { 14 | demo({id: 'demo1', count: 0}); 15 | demo({id: 'demo2', count: 1, upvoted: true}); 16 | demo({id: 'demo3', count: -1, downvoted: true}); 17 | demo({id: 'unix', count: 3, upvoted: true, starred: true, skin: 'unix'}); 18 | demo({id: 'serverfault', count: 3, upvoted: true, starred: true, skin: 'serverfault'}); 19 | demo({id: 'superuser', count: 3, upvoted: true, starred: true, skin: 'superuser'}); 20 | } 21 | 22 | describe('initialize from params', () => { 23 | describe('for empty params', () => { 24 | const obj = gen(); 25 | it('should have state count 0, not upvoted, not downvoted, not starred', () => { 26 | assert.equal(obj.count(), 0); 27 | assert.equal(obj.upvoted(), false); 28 | assert.equal(obj.downvoted(), false); 29 | assert.equal(obj.starred(), false); 30 | }); 31 | }); 32 | 33 | describe('for explicitly set fields', () => { 34 | it('should have given values', () => { 35 | assert.equal(gen({count: 17}).count(), 17); 36 | assert.equal(gen({upvoted: true}).upvoted(), true); 37 | assert.equal(gen({downvoted: true}).downvoted(), true); 38 | assert.equal(gen({starred: true}).starred(), true); 39 | }); 40 | }); 41 | 42 | describe('for explicitly set fields of invalid type', () => { 43 | it('should throw', () => { 44 | assert.throws(() => gen({count: 'foo'}), /"count" must be a valid integer/); 45 | assert.throws(() => gen({upvoted: 'foo'}), /"upvoted" must be a boolean/); 46 | assert.throws(() => gen({downvoted: 'foo'}), /"downvoted" must be a boolean/); 47 | assert.throws(() => gen({starred: 'foo'}), /"starred" must be a boolean/); 48 | assert.throws(() => gen({callback: 'foo'}), /"callback" must be a function/); 49 | }); 50 | }); 51 | 52 | describe('for other kind of invalid parameters', () => { 53 | it('should throw', () => { 54 | assert.throws(() => gen({upvoted: true, downvoted: true}), /must not be true at the same time/); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('initialize from dom', () => { 60 | it('should pick up values from the DOM', () => { 61 | const v1 = Upvote.create(Utils.addNewDom({count: 1}).attr('id')); 62 | assert.equal(v1.count(), 1); 63 | assert.equal(v1.upvoted(), false); 64 | assert.equal(v1.downvoted(), false); 65 | assert.equal(v1.starred(), false); 66 | 67 | const v2 = Upvote.create(Utils.addNewDom({count: 2, upvoted: true}).attr('id')); 68 | assert.equal(v2.count(), 2); 69 | assert.equal(v2.upvoted(), true); 70 | assert.equal(v2.downvoted(), false); 71 | assert.equal(v2.starred(), false); 72 | 73 | const v3 = Upvote.create(Utils.addNewDom({count: 3, upvoted: true, starred: true}).attr('id')); 74 | assert.equal(v3.count(), 3); 75 | assert.equal(v3.upvoted(), true); 76 | assert.equal(v3.downvoted(), false); 77 | assert.equal(v3.starred(), true); 78 | 79 | const v4 = Upvote.create(Utils.addNewDom({count: 4, downvoted: true}).attr('id')); 80 | assert.equal(v4.count(), 4); 81 | assert.equal(v4.upvoted(), false); 82 | assert.equal(v4.downvoted(), true); 83 | assert.equal(v4.starred(), false); 84 | 85 | const v5 = Upvote.create(Utils.addNewDom({count: 5, downvoted: true, starred: true}).attr('id')); 86 | assert.equal(v5.count(), 5); 87 | assert.equal(v5.upvoted(), false); 88 | assert.equal(v5.downvoted(), true); 89 | assert.equal(v5.starred(), true); 90 | 91 | const vLarge = Upvote.create(Utils.addNewDom({count: 456789}).attr('id')); 92 | assert.equal(vLarge.count(), 456789); 93 | assert.equal(vLarge.upvoted(), false); 94 | assert.equal(vLarge.downvoted(), false); 95 | assert.equal(vLarge.starred(), false); 96 | 97 | const vNegativeLarge = Upvote.create(Utils.addNewDom({count: -456789}).attr('id')); 98 | assert.equal(vNegativeLarge.count(), -456789); 99 | assert.equal(vNegativeLarge.upvoted(), false); 100 | assert.equal(vNegativeLarge.downvoted(), false); 101 | assert.equal(vNegativeLarge.starred(), false); 102 | }); 103 | 104 | it('should throw when DOM defines upvoted + downvoted', () => { 105 | assert.throws(() => Upvote.create(Utils.addNewDom({upvoted: true, downvoted: true}))); 106 | }); 107 | }); 108 | 109 | describe('UI gets updated from initial params', () => { 110 | describe('for empty params', () => { 111 | const obj = uiTester(gen()); 112 | it('should have state count 0, not upvoted, not downvoted, not starred', () => { 113 | assert.equal(obj.count(), 0); 114 | assert.equal(obj.upvoted(), false); 115 | assert.equal(obj.downvoted(), false); 116 | assert.equal(obj.starred(), false); 117 | }); 118 | }); 119 | 120 | describe('for explicitly set fields', () => { 121 | it('should have given values', () => { 122 | assert.equal(uiTester(gen({count: 17})).count(), 17); 123 | assert.equal(uiTester(gen({upvoted: true})).upvoted(), true); 124 | assert.equal(uiTester(gen({downvoted: true})).downvoted(), true); 125 | assert.equal(uiTester(gen({starred: true})).starred(), true); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('upvote and un-upvote', () => { 131 | const obj = gen({count: 5}); 132 | it('should increment count on upvote', () => { 133 | obj.upvote(); 134 | assert.equal(obj.count(), 6); 135 | assert.equal(obj.upvoted(), true); 136 | }); 137 | it('should decrement count on upvote again', () => { 138 | obj.upvote(); 139 | assert.equal(obj.count(), 5); 140 | assert.equal(obj.upvoted(), false); 141 | }); 142 | }); 143 | 144 | describe('downvote and un-downvote', () => { 145 | const obj = gen({count: 5}); 146 | it('should decrement count on downvote', () => { 147 | obj.downvote(); 148 | assert.equal(obj.count(), 4); 149 | assert.equal(obj.downvoted(), true); 150 | }); 151 | it('should increment count on downvote again', () => { 152 | obj.downvote(); 153 | assert.equal(obj.count(), 5); 154 | assert.equal(obj.downvoted(), false); 155 | }); 156 | }); 157 | 158 | describe('upvote and downvote', () => { 159 | it('should increment count by 2 on upvote downvoted', () => { 160 | const obj = gen({count: 5, downvoted: true}); 161 | obj.upvote(); 162 | assert.equal(obj.count(), 7); 163 | assert.equal(obj.upvoted(), true); 164 | assert.equal(obj.downvoted(), false); 165 | }); 166 | it('should decrement count by 2 on downvote upvoted', () => { 167 | const obj = gen({count: 5, upvoted: true}); 168 | obj.downvote(); 169 | assert.equal(obj.count(), 3); 170 | assert.equal(obj.upvoted(), false); 171 | assert.equal(obj.downvoted(), true); 172 | }); 173 | }); 174 | 175 | describe('star and un-star', () => { 176 | const obj = gen(); 177 | it('should set starred on star', () => { 178 | obj.star(); 179 | assert.equal(obj.starred(), true); 180 | }); 181 | it('should unset starred on star again', () => { 182 | obj.star(); 183 | assert.equal(obj.starred(), false); 184 | }); 185 | }); 186 | 187 | describe('separate widgets', () => { 188 | it('should get upvoted independently', () => { 189 | const v1 = gen({count: 10}); 190 | const v2 = gen({count: 20}); 191 | v1.upvote(); 192 | assert.equal(v1.count(), 11); 193 | assert.equal(v1.upvoted(), true); 194 | assert.equal(v2.count(), 20); 195 | assert.equal(v2.upvoted(), false); 196 | }); 197 | 198 | it('should get downvoted independently', () => { 199 | const v1 = gen({count: 10}); 200 | const v2 = gen({count: 20}); 201 | v1.downvote(); 202 | assert.equal(v1.count(), 9); 203 | assert.equal(v1.downvoted(), true); 204 | assert.equal(v2.count(), 20); 205 | assert.equal(v2.downvoted(), false); 206 | }); 207 | 208 | it('should get starred independently', () => { 209 | const v1 = gen({count: 10}); 210 | const v2 = gen({count: 20}); 211 | v1.star(); 212 | assert.equal(v1.starred(), true); 213 | assert.equal(v2.starred(), false); 214 | }); 215 | }); 216 | 217 | describe('callback', () => { 218 | var receivedPayload; 219 | const callback = payload => receivedPayload = payload; 220 | 221 | const obj1_origCount = 10; 222 | const obj1 = gen({count: obj1_origCount, callback: callback}); 223 | const obj1_id = obj1.id; 224 | 225 | const obj2_origCount = 20; 226 | const obj2 = gen({count: obj2_origCount, callback: callback}); 227 | const obj2_id = obj2.id; 228 | 229 | it('should get triggered with correct payload on obj1.upvote', () => { 230 | obj1.upvote(); 231 | assert.deepEqual(receivedPayload, { 232 | id: obj1_id, 233 | action: 'upvote', 234 | newState: { 235 | count: obj1_origCount + 1, 236 | downvoted: false, 237 | upvoted: true, 238 | starred: false 239 | } 240 | }); 241 | }); 242 | 243 | it('should get triggered with correct payload on obj2.upvote', () => { 244 | obj2.upvote(); 245 | assert.deepEqual(receivedPayload, { 246 | id: obj2_id, 247 | action: 'upvote', 248 | newState: { 249 | count: obj2_origCount + 1, 250 | downvoted: false, 251 | upvoted: true, 252 | starred: false 253 | } 254 | }); 255 | }); 256 | 257 | it('should get triggered with correct payload on obj1.upvote', () => { 258 | obj1.upvote(); 259 | assert.deepEqual(receivedPayload, { 260 | id: obj1_id, 261 | action: 'unupvote', 262 | newState: { 263 | count: obj1_origCount, 264 | downvoted: false, 265 | upvoted: false, 266 | starred: false 267 | } 268 | }); 269 | }); 270 | 271 | it('should get triggered with correct payload on obj2.star', () => { 272 | obj2.star(); 273 | assert.deepEqual(receivedPayload, { 274 | id: obj2_id, 275 | action: 'star', 276 | newState: { 277 | count: obj2_origCount + 1, 278 | downvoted: false, 279 | upvoted: true, 280 | starred: true 281 | } 282 | }); 283 | }); 284 | 285 | it('should get triggered with correct payload on obj2.star', () => { 286 | obj2.star(); 287 | assert.deepEqual(receivedPayload, { 288 | id: obj2_id, 289 | action: 'unstar', 290 | newState: { 291 | count: obj2_origCount + 1, 292 | downvoted: false, 293 | upvoted: true, 294 | starred: false 295 | } 296 | }); 297 | }); 298 | 299 | it('should get triggered with correct payload on obj2.downvote', () => { 300 | obj2.downvote(); 301 | assert.deepEqual(receivedPayload, { 302 | id: obj2_id, 303 | action: 'downvote', 304 | newState: { 305 | count: obj2_origCount - 1, 306 | downvoted: true, 307 | upvoted: false, 308 | starred: false 309 | } 310 | }); 311 | }); 312 | 313 | it('should get triggered with correct payload on obj2.downvote', () => { 314 | obj2.downvote(); 315 | assert.deepEqual(receivedPayload, { 316 | id: obj2_id, 317 | action: 'undownvote', 318 | newState: { 319 | count: obj2_origCount, 320 | downvoted: false, 321 | upvoted: false, 322 | starred: false 323 | } 324 | }); 325 | }); 326 | }); 327 | 328 | describe('changes to model should propagate to UI', () => { 329 | const obj = gen(); 330 | const ui = uiTester(obj); 331 | 332 | it('should increment count on upvote', () => { 333 | obj.upvote(); 334 | assert.equal(ui.count(), 1); 335 | assert.equal(ui.upvoted(), true); 336 | }); 337 | 338 | it('should decrement count on downvote', () => { 339 | obj.downvote(); 340 | assert.equal(ui.count(), -1); 341 | assert.equal(ui.upvoted(), false); 342 | assert.equal(ui.downvoted(), true); 343 | }); 344 | 345 | it('should un-downvote, upvote, and increment count on upvote', () => { 346 | obj.upvote(); 347 | assert.equal(ui.count(), 1); 348 | assert.equal(ui.upvoted(), true); 349 | assert.equal(ui.downvoted(), false); 350 | }); 351 | 352 | it('should toggle starred state on star', () => { 353 | obj.star(); 354 | assert.equal(ui.starred(), true); 355 | }); 356 | 357 | it('should clear starred state on star again', () => { 358 | obj.star(); 359 | assert.equal(ui.starred(), false); 360 | }); 361 | }); 362 | 363 | describe('changes by user should propagate to model', () => { 364 | const obj = gen(); 365 | const ui = uiTester(obj); 366 | 367 | it('should increment count on upvote', () => { 368 | ui.upvote(); 369 | assert.equal(obj.count(), 1); 370 | assert.equal(obj.upvoted(), true); 371 | }); 372 | 373 | it('should decrement count on downvote', () => { 374 | ui.downvote(); 375 | assert.equal(obj.count(), -1); 376 | assert.equal(obj.upvoted(), false); 377 | assert.equal(obj.downvoted(), true); 378 | }); 379 | 380 | it('should un-downvote, upvote, and increment count on upvote', () => { 381 | ui.upvote(); 382 | assert.equal(obj.count(), 1); 383 | assert.equal(obj.upvoted(), true); 384 | assert.equal(obj.downvoted(), false); 385 | }); 386 | 387 | it('should toggle starred state on star', () => { 388 | ui.star(); 389 | assert.equal(obj.starred(), true); 390 | }); 391 | 392 | it('should clear starred state on star again', () => { 393 | ui.star(); 394 | assert.equal(obj.starred(), false); 395 | }); 396 | }); 397 | 398 | describe('associating multiple models to same id', () => { 399 | it('should throw', () => { 400 | const orig = gen(); 401 | assert.throws(() => gen({id: orig.id}), /already in use by another upvote controller/); 402 | }); 403 | }); 404 | 405 | describe('after destroy', () => { 406 | it('should stop responding to clicks', () => { 407 | const obj = gen({count: 99}); 408 | const ui = uiTester(obj); 409 | 410 | ui.upvote(); 411 | assert.equal(ui.count(), 100); 412 | ui.upvote(); 413 | assert.equal(ui.count(), 99); 414 | 415 | obj.destroy(); 416 | ui.upvote(); 417 | assert.equal(ui.count(), 99); 418 | assert.throws(() => obj.upvote()); 419 | assert.throws(() => obj.downvote()); 420 | assert.throws(() => obj.star()); 421 | assert.throws(() => obj.count()); 422 | assert.throws(() => obj.upvoted()); 423 | assert.throws(() => obj.downvoted()); 424 | assert.throws(() => obj.starred()); 425 | 426 | const reused = gen({id: obj.id}); 427 | assert.equal(reused.count(), 99); 428 | ui.upvote(); 429 | assert.equal(reused.count(), 100); 430 | }); 431 | }); 432 | 433 | describe('all sub-elements (upvote/downvote/count/star) are optional in the HTML markup', () => { 434 | ['upvote', 'downvote', 'count', 'star'].forEach(cls => { 435 | it(`can be initialized without ${cls} sub-element`, () => { 436 | const jqdom = Utils.addNewDom(); 437 | jqdom.find('.' + cls).remove(); 438 | const obj = Setup.create(jqdom.attr('id'), {}, jqdom); 439 | 440 | assert.equal(obj.count(), 0); 441 | obj.upvote(); 442 | assert.equal(obj.count(), 1); 443 | assert.equal(obj.upvoted(), true); 444 | obj.downvote(); 445 | assert.equal(obj.count(), -1); 446 | assert.equal(obj.downvoted(), true); 447 | assert.equal(obj.upvoted(), false); 448 | obj.downvote(); 449 | assert.equal(obj.count(), 0); 450 | assert.equal(obj.downvoted(), false); 451 | obj.star(); 452 | assert.equal(obj.starred(), true); 453 | obj.star(); 454 | assert.equal(obj.starred(), false); 455 | }); 456 | }); 457 | }); 458 | } 459 | }; 460 | 461 | if (typeof module !== 'undefined' && module.exports) { 462 | exports.Tests = Tests; 463 | } 464 | -------------------------------------------------------------------------------- /tests/lib/setup.js: -------------------------------------------------------------------------------- 1 | const CliSetup = () => { 2 | const jsdom = require('jsdom'); 3 | const window2 = new jsdom.JSDOM().window; 4 | const jQuery = require('jquery')(window2); 5 | 6 | return { 7 | chai: require('chai'), 8 | jQuery: jQuery, 9 | Utils: require('./utils.js').Utils(window2, jQuery), 10 | Tests: require('./common-tests.js').Tests, 11 | Upvote: require('../../dist/upvotejs/upvotejs.vanilla.js')(window2) 12 | }; 13 | }; 14 | 15 | const BrowserSetup = () => { 16 | return { 17 | demo: true, 18 | chai: chai, 19 | jQuery: jQuery, 20 | Utils: Utils(window, $), 21 | Tests: Tests, 22 | Upvote: Upvote 23 | }; 24 | }; 25 | 26 | if (typeof module !== 'undefined' && module.exports) { 27 | exports.Setup = CliSetup(); 28 | } else { 29 | var Setup = BrowserSetup(); 30 | } 31 | -------------------------------------------------------------------------------- /tests/lib/utils.js: -------------------------------------------------------------------------------- 1 | const Utils = (window, $) => { 2 | const document = window.document; 3 | $(document.body).append('