├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .hound.yml ├── .npmignore ├── .travis.yml ├── README.md ├── dist ├── ko-component-tester.js └── ko-component-tester.min.js ├── karma.conf.js ├── karma.travis.js ├── package.json ├── src └── index.js ├── test ├── $context.spec.js ├── $data.spec.js ├── .eslintrc ├── getComponentParams.spec.js ├── renderComponent.spec.js ├── renderHtml.spec.js ├── sample.spec.js ├── waitForBinding.spec.js └── waitForProperty.spec.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "profiscience", 3 | "env": { 4 | "mocha": true 5 | }, 6 | "parserOptions": { 7 | "sourceType": "script" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | coverage/ 4 | 5 | node_modules/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | javascript: 2 | enabled: false 3 | ignore_file: .eslintignore 4 | eslint: 5 | enabled: true 6 | 7 | fail_on_violations: true 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .git/ 4 | .gitignore 5 | 6 | .eslintrc 7 | 8 | src/ 9 | example/ 10 | coverage/ 11 | 12 | index.html 13 | README.md 14 | test.js 15 | karma.config.* 16 | webpack.config.* 17 | 18 | node_modules/ 19 | npm-debug.log 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | 6 | addons: 7 | firefox: "latest" 8 | 9 | before_script: 10 | - export DISPLAY=:99.0 11 | - sh -e /etc/init.d/xvfb start 12 | 13 | after_script: 14 | - ./node_modules/coveralls/bin/coveralls.js < coverage/lcov.txt 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ko-component-tester 2 | 3 | [![NPM](https://img.shields.io/npm/v/ko-component-tester.svg)](https://www.npmjs.com/package/ko-component-tester) 4 | ![WTFPL](https://img.shields.io/npm/l/ko-component-tester.svg) 5 | [![Travis](https://img.shields.io/travis/Profiscience/ko-component-tester.svg)](https://travis-ci.org/Profiscience/ko-component-tester) 6 | [![Coverage Status](https://coveralls.io/repos/github/Profiscience/ko-component-tester/badge.svg?branch=master)](https://coveralls.io/github/Profiscience/ko-component-tester?branch=master) 7 | [![Dependency Status](https://img.shields.io/david/Profiscience/ko-component-tester.svg)](https://david-dm.org/Profiscience/ko-component-tester) 8 | [![Peer Dependency Status](https://img.shields.io/david/peer/Profiscience/ko-component-tester.svg?maxAge=2592000)](https://david-dm.org/Profiscience/ko-component-tester#info=peerDependencies&view=table) 9 | [![NPM Downloads](https://img.shields.io/npm/dt/ko-component-tester.svg?maxAge=2592000)](http://npm-stat.com/charts.html?package=ko-component-tester&author=&from=&to=) 10 | 11 | _TDD Helpers for Knockout components and bindings_ 12 | 13 | #### Sample tests for a Knockout binding 14 | 15 | ```javascript 16 | 'use strict' 17 | 18 | const { renderHtml } = require('ko-component-tester') 19 | const { expect } = require('chai') 20 | 21 | describe ('Hello World text-binding', () => { 22 | let $el 23 | beforeEach(() => { 24 | $el = renderHtml({ 25 | template: `
`, 26 | viewModel: { greeting: 'Hello World'} 27 | }) 28 | }) 29 | it('renders', () => { 30 | expect($el).to.exist 31 | }) 32 | it('renders correct text', () => { 33 | expect($el.html()).equals('Hello World') 34 | }) 35 | }) 36 | ``` 37 | 38 | #### Sample tests for a Knockout component 39 | 40 | ```javascript 41 | 'use strict' 42 | 43 | const { renderComponent } = require('ko-component-tester') 44 | const { expect } = require('chai') 45 | 46 | describe('Hello World Component' , () => { 47 | let $el 48 | beforeEach(() => { 49 | $el = renderComponent({ 50 | template: ``, 51 | viewModel: function() { this.greeting = 'Hello World' } 52 | }) 53 | }) 54 | afterEach(() => { 55 | $el.dispose() 56 | }) 57 | it('renders', () => { 58 | expect($el).to.exist 59 | }) 60 | it('renders correct content', () => { 61 | expect($el.html()).contains('Hello World') 62 | }) 63 | }) 64 | ``` 65 | 66 | #### Sample Login test 67 | 68 | ```javascript 69 | 'use strict' 70 | 71 | const ko = require('knockout') 72 | const { expect } = require('chai') 73 | const sinon = require('sinon') 74 | const { renderComponent } = require('../src') 75 | 76 | class LoginComponent { 77 | constructor() { 78 | this.username = ko.observable() 79 | this.password = ko.observable() 80 | } 81 | submit() {} 82 | } 83 | 84 | describe('sample login component' , () => { 85 | let $el 86 | 87 | before(() => { 88 | $el = renderComponent({ 89 | viewModel: LoginComponent, 90 | template: ` 91 |
92 | 93 | 94 | 95 |
` 96 | }) 97 | }) 98 | 99 | after(() => { 100 | $el.dispose() 101 | }) 102 | 103 | it('renders correctly', () => { 104 | expect($el).to.exist 105 | expect($el.find('form'), 'contains a form').to.exist 106 | expect($el.find('input[name="user"]', 'contains a username field')).to.exist 107 | expect($el.find('input[name="pass"]', 'contains a password field')).to.exist 108 | expect($el.find('input[type="submit"]', 'contains a submit button')).to.exist 109 | }) 110 | 111 | it('updates the viewmodel when a value is changed', () => { 112 | $el.find('input[name=user]').simulate('change', 'john') 113 | expect($el.$data().username()).equals('john') 114 | }) 115 | 116 | it('can submit the form', () => { 117 | const submitSpy = sinon.spy($el.$data().submit) 118 | $el.find('input[name=user]').simulate('change', 'john') 119 | $el.find('input[name=pass]').simulate('change', 'p455w0rd') 120 | $el.find('input[type="submit"]').simulate('click') 121 | 122 | expect(submitSpy).to.be.called 123 | }) 124 | }) 125 | 126 | ``` 127 | 128 | #### renderHtml(options) 129 | 130 | returns a jQuery element containing the rendered html output 131 | 132 | - `options.template` - a string of html to be rendered 133 | - `options.viewModel` - an object, function, or class 134 | 135 | Example with viewModel function: 136 | 137 | ```javascript 138 | const options = { 139 | template: `
`, 140 | viewModel: function() { this.greeting = 'Hello Text Binding' } 141 | } 142 | const $el = renderHtml(options) 143 | ``` 144 | 145 | Example with viewModel class: 146 | 147 | ```javascript 148 | const options = { 149 | template: `
`, 150 | viewModel: class ViewModel { 151 | constructor() { 152 | this.greeting = 'Hello Text Binding' 153 | } 154 | } 155 | } 156 | const $el = renderHtml(options) 157 | ``` 158 | 159 | Example with viewModel object: 160 | 161 | ```javascript 162 | const options = { 163 | template: `
`, 164 | viewModel: { greeting: 'Hello Text Binding' } 165 | } 166 | const $el = renderHtml(options) 167 | ``` 168 | [See spec for more examples of renderHtml().](test/renderHtml.spec.js) 169 | 170 | 171 | #### renderComponent(component, params, bindingContext) 172 | 173 | returns a jQuery element containing the rendered html output 174 | 175 | - `component.template` - a string of html to be rendered 176 | - `component.viewModel` - a function, class, or instance 177 | - `params` - optional params to be passed into the viewModel's constructor 178 | - `bindingContext` - optional bindingContext to inject (useful for stubbing `$parent` or `$index`) 179 | 180 | Example with viewModel function: 181 | 182 | ```javascript 183 | const component = { 184 | template: `
`, 185 | viewModel: function() { this.greeting = 'Hello Text Binding' } 186 | } 187 | const $el = renderComponent(component) 188 | // $el.dispose() 189 | ``` 190 | 191 | Example with viewModel class: 192 | 193 | ```javascript 194 | const component = { 195 | template: `
`, 196 | viewModel: class ViewModel { 197 | constructor(params) { 198 | this.greeting = params.greeting 199 | } 200 | } 201 | } 202 | const params = { 203 | greeting: 'Hello Text Binding' 204 | } 205 | const $el = renderComponent(component, params) 206 | // $el.dispose() 207 | ``` 208 | 209 | Example with viewModel instance: 210 | 211 | ```javascript 212 | class ViewModel { 213 | constructor(params) { 214 | this.greeting = params.greeting 215 | } 216 | } 217 | const component = { 218 | template: `
`, 219 | viewModel: { instance: new ViewModel(params) } 220 | } 221 | const $el = renderComponent(component) 222 | // $el.dispose() 223 | ``` 224 | 225 | [See spec for more examples of renderComponent().](test/renderComponent.spec.js) 226 | 227 | #### $el.getComponentParams() 228 | 229 | [see spec for examples](test/getComponentParams.spec.js) 230 | 231 | #### $el.waitForBinding() 232 | 233 | [see spec for examples](test/waitForBinding.spec.js) 234 | 235 | #### $el.waitForProperty() 236 | 237 | [see spec for examples](test/waitForProperty.spec.js) 238 | 239 | #### $el.simulate(event, value) 240 | 241 | - `event` - the event to simulate, eg `'click', or 'change'` 242 | - `value` - if provided this value will be assigned. It's handy for assigning a value to a textbox and triggering a `change` event like this. 243 | 244 | ```javascript 245 | // simulate changing the value of a textbox 246 | $input.simulate('change', 'new value') 247 | // simulate clicking a button 248 | $submit.simulate('click') 249 | ``` 250 | 251 | #### Attribution 252 | 253 | https://github.com/jeremija/kotest 254 | -------------------------------------------------------------------------------- /dist/ko-component-tester.min.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory(require("knockout"),require("jquery"));else if(typeof define==="function"&&define.amd)define(["knockout","jquery"],factory);else if(typeof exports==="object")exports["ko-component-tester"]=factory(require("knockout"),require("jquery"));else root["ko-component-tester"]=factory(root["ko"],root["jQuery"])})(this,function(__WEBPACK_EXTERNAL_MODULE_1__,__WEBPACK_EXTERNAL_MODULE_2__){return function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:false};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.loaded=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.p="";return __webpack_require__(0)}([function(module,exports,__webpack_require__){"use strict";function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function")}}function _toArray(arr){return Array.isArray(arr)?arr:Array.from(arr)}var ko=__webpack_require__(1);var $=__webpack_require__(2);var _=__webpack_require__(3);var simulateEvent=__webpack_require__(5);$.fn.simulate=function(eventName,value){if(value){this.val(value)}if(value)this.val(value);var target=this.get(0);simulateEvent.simulate(target,eventName,value);ko.tasks.runEarly()};$.fn.waitForBinding=function(bindingName){var _this=this,_arguments=arguments;if(!ko.bindingHandlers[bindingName])throw new Error("binding does not exist: "+bindingName);var binding=ko.bindingHandlers[bindingName].init?ko.bindingHandlers[bindingName].init.bind(ko.bindingHandlers[bindingName].init):function(){};return new Promise(function(resolve){var $el=_this;ko.bindingHandlers[bindingName].init=function(el){if($el.get(0)===el){binding.apply(undefined,_arguments);ko.tasks.schedule(function(){ko.bindingHandlers[bindingName].init=binding;resolve($el)})}else{binding.apply(undefined,_arguments)}}})};$.fn.waitForProperty=function(key,val){var timeout=arguments.length>2&&arguments[2]!==undefined?arguments[2]:2e3;var prop=access(key.split("."),this.$data());return new Promise(function(resolve,reject){if(matches(prop())){return resolve(prop())}var timeoutId=setTimeout(function(){killMe.dispose();reject("Timed out waiting for property "+key)},timeout);var killMe=prop.subscribe(function(v){if(!matches(v)){return}clearTimeout(timeoutId);killMe.dispose();ko.tasks.runEarly();resolve(v)})});function access(_ref,obj){var _ref2=_toArray(_ref),k=_ref2[0],ks=_ref2.slice(1);var p=obj[k];return ks.length>0?access(ks,p):p}function matches(v){return typeof v!=="undefined"&&(typeof val==="undefined"||(val instanceof RegExp?val.test(v):v===val))}};$.fn.$data=function(){return this.children().length>0?ko.dataFor(this.children().get(0)):ko.dataFor(ko.virtualElements.firstChild(this.get(0)))};$.fn.$context=function(){return this.children().length>0?ko.contextFor(this.children().get(0)):ko.contextFor(ko.virtualElements.firstChild(this.get(0)))};ko.components.loaders.unshift({loadComponent:function loadComponent(name,component,done){if(!component.viewModel){var ViewModel=function ViewModel(params){_classCallCheck(this,ViewModel);ko.utils.extend(this,params)};component.viewModel=ViewModel}done(null)},loadViewModel:function loadViewModel(name,config,done){if(typeof config==="function"){done(function(params){var viewModel=new config(params);viewModel._calledWith=params;return viewModel},done)}else if(config.createViewModel){done(function(params,componentInfo){var viewModel=config.createViewModel(params,componentInfo);viewModel._calledWith=params;return viewModel},done)}else{done(null)}}});$.fn.getComponentParams=function(){return ko.contextFor(ko.virtualElements.firstChild(this.get(0))).$component._calledWith};function renderComponent(component){var _params=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};var _bindingCtx=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};var _component=ko.observable("_SUT");var $el=$('
');component.synchronous=true;if(ko.components.isRegistered("_SUT")){ko.components.unregister("_SUT")}ko.components.register("_SUT",component);ko.bindingHandlers._setContext={init:function init(el,valueAccessor,allBindings,viewModel,bindingContext){_.merge(bindingContext,_bindingCtx)}};$("body").html($el);ko.applyBindings(_.merge({_component:_component,_params:_params}),$el.get(0));ko.tasks.runEarly();ko.components.unregister("_SUT");ko.bindingHandlers._setContext=void 0;$el.dispose=function(){ko.components.register("_NULL",{template:""});_component("_NULL");ko.tasks.runEarly();ko.components.unregister("_NULL");$el.remove()};return $el}function renderHtml(_ref3){var template=_ref3.template,_ref3$viewModel=_ref3.viewModel,viewModel=_ref3$viewModel===undefined?{}:_ref3$viewModel;var $el=void 0;try{$el=$(template)}catch(e){$el=$("").text(template)}$("body").html($el);if(typeof viewModel==="function"){ko.applyBindings(new viewModel,$el.get(0))}else{ko.applyBindings(viewModel,$el.get(0))}ko.tasks.runEarly();return $el}module.exports={renderComponent:renderComponent,renderHtml:renderHtml}},function(module,exports){module.exports=__WEBPACK_EXTERNAL_MODULE_1__},function(module,exports){module.exports=__WEBPACK_EXTERNAL_MODULE_2__},function(module,exports,__webpack_require__){var __WEBPACK_AMD_DEFINE_RESULT__;(function(global,module){(function(){var undefined;var VERSION="4.17.4";var LARGE_ARRAY_SIZE=200;var CORE_ERROR_TEXT="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",FUNC_ERROR_TEXT="Expected a function";var HASH_UNDEFINED="__lodash_hash_undefined__";var MAX_MEMOIZE_SIZE=500;var PLACEHOLDER="__lodash_placeholder__";var CLONE_DEEP_FLAG=1,CLONE_FLAT_FLAG=2,CLONE_SYMBOLS_FLAG=4;var COMPARE_PARTIAL_FLAG=1,COMPARE_UNORDERED_FLAG=2;var WRAP_BIND_FLAG=1,WRAP_BIND_KEY_FLAG=2,WRAP_CURRY_BOUND_FLAG=4,WRAP_CURRY_FLAG=8,WRAP_CURRY_RIGHT_FLAG=16,WRAP_PARTIAL_FLAG=32,WRAP_PARTIAL_RIGHT_FLAG=64,WRAP_ARY_FLAG=128,WRAP_REARG_FLAG=256,WRAP_FLIP_FLAG=512;var DEFAULT_TRUNC_LENGTH=30,DEFAULT_TRUNC_OMISSION="...";var HOT_COUNT=800,HOT_SPAN=16;var LAZY_FILTER_FLAG=1,LAZY_MAP_FLAG=2,LAZY_WHILE_FLAG=3;var INFINITY=1/0,MAX_SAFE_INTEGER=9007199254740991,MAX_INTEGER=1.7976931348623157e308,NAN=0/0;var MAX_ARRAY_LENGTH=4294967295,MAX_ARRAY_INDEX=MAX_ARRAY_LENGTH-1,HALF_MAX_ARRAY_LENGTH=MAX_ARRAY_LENGTH>>>1;var wrapFlags=[["ary",WRAP_ARY_FLAG],["bind",WRAP_BIND_FLAG],["bindKey",WRAP_BIND_KEY_FLAG],["curry",WRAP_CURRY_FLAG],["curryRight",WRAP_CURRY_RIGHT_FLAG],["flip",WRAP_FLIP_FLAG],["partial",WRAP_PARTIAL_FLAG],["partialRight",WRAP_PARTIAL_RIGHT_FLAG],["rearg",WRAP_REARG_FLAG]];var argsTag="[object Arguments]",arrayTag="[object Array]",asyncTag="[object AsyncFunction]",boolTag="[object Boolean]",dateTag="[object Date]",domExcTag="[object DOMException]",errorTag="[object Error]",funcTag="[object Function]",genTag="[object GeneratorFunction]",mapTag="[object Map]",numberTag="[object Number]",nullTag="[object Null]",objectTag="[object Object]",promiseTag="[object Promise]",proxyTag="[object Proxy]",regexpTag="[object RegExp]",setTag="[object Set]",stringTag="[object String]",symbolTag="[object Symbol]",undefinedTag="[object Undefined]",weakMapTag="[object WeakMap]",weakSetTag="[object WeakSet]";var arrayBufferTag="[object ArrayBuffer]",dataViewTag="[object DataView]",float32Tag="[object Float32Array]",float64Tag="[object Float64Array]",int8Tag="[object Int8Array]",int16Tag="[object Int16Array]",int32Tag="[object Int32Array]",uint8Tag="[object Uint8Array]",uint8ClampedTag="[object Uint8ClampedArray]",uint16Tag="[object Uint16Array]",uint32Tag="[object Uint32Array]";var reEmptyStringLeading=/\b__p \+= '';/g,reEmptyStringMiddle=/\b(__p \+=) '' \+/g,reEmptyStringTrailing=/(__e\(.*?\)|\b__t\)) \+\n'';/g;var reEscapedHtml=/&(?:amp|lt|gt|quot|#39);/g,reUnescapedHtml=/[&<>"']/g,reHasEscapedHtml=RegExp(reEscapedHtml.source),reHasUnescapedHtml=RegExp(reUnescapedHtml.source);var reEscape=/<%-([\s\S]+?)%>/g,reEvaluate=/<%([\s\S]+?)%>/g,reInterpolate=/<%=([\s\S]+?)%>/g;var reIsDeepProp=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,reIsPlainProp=/^\w*$/,reLeadingDot=/^\./,rePropName=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;var reRegExpChar=/[\\^$.*+?()[\]{}|]/g,reHasRegExpChar=RegExp(reRegExpChar.source);var reTrim=/^\s+|\s+$/g,reTrimStart=/^\s+/,reTrimEnd=/\s+$/;var reWrapComment=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,reWrapDetails=/\{\n\/\* \[wrapped with (.+)\] \*/,reSplitDetails=/,? & /;var reAsciiWord=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;var reEscapeChar=/\\(\\)?/g;var reEsTemplate=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;var reFlags=/\w*$/;var reIsBadHex=/^[-+]0x[0-9a-f]+$/i;var reIsBinary=/^0b[01]+$/i;var reIsHostCtor=/^\[object .+?Constructor\]$/;var reIsOctal=/^0o[0-7]+$/i;var reIsUint=/^(?:0|[1-9]\d*)$/;var reLatin=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;var reNoMatch=/($^)/;var reUnescapedString=/['\n\r\u2028\u2029\\]/g;var rsAstralRange="\\ud800-\\udfff",rsComboMarksRange="\\u0300-\\u036f",reComboHalfMarksRange="\\ufe20-\\ufe2f",rsComboSymbolsRange="\\u20d0-\\u20ff",rsComboRange=rsComboMarksRange+reComboHalfMarksRange+rsComboSymbolsRange,rsDingbatRange="\\u2700-\\u27bf",rsLowerRange="a-z\\xdf-\\xf6\\xf8-\\xff",rsMathOpRange="\\xac\\xb1\\xd7\\xf7",rsNonCharRange="\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf",rsPunctuationRange="\\u2000-\\u206f",rsSpaceRange=" \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",rsUpperRange="A-Z\\xc0-\\xd6\\xd8-\\xde",rsVarRange="\\ufe0e\\ufe0f",rsBreakRange=rsMathOpRange+rsNonCharRange+rsPunctuationRange+rsSpaceRange;var rsApos="['’]",rsAstral="["+rsAstralRange+"]",rsBreak="["+rsBreakRange+"]",rsCombo="["+rsComboRange+"]",rsDigits="\\d+",rsDingbat="["+rsDingbatRange+"]",rsLower="["+rsLowerRange+"]",rsMisc="[^"+rsAstralRange+rsBreakRange+rsDigits+rsDingbatRange+rsLowerRange+rsUpperRange+"]",rsFitz="\\ud83c[\\udffb-\\udfff]",rsModifier="(?:"+rsCombo+"|"+rsFitz+")",rsNonAstral="[^"+rsAstralRange+"]",rsRegional="(?:\\ud83c[\\udde6-\\uddff]){2}",rsSurrPair="[\\ud800-\\udbff][\\udc00-\\udfff]",rsUpper="["+rsUpperRange+"]",rsZWJ="\\u200d";var rsMiscLower="(?:"+rsLower+"|"+rsMisc+")",rsMiscUpper="(?:"+rsUpper+"|"+rsMisc+")",rsOptContrLower="(?:"+rsApos+"(?:d|ll|m|re|s|t|ve))?",rsOptContrUpper="(?:"+rsApos+"(?:D|LL|M|RE|S|T|VE))?",reOptMod=rsModifier+"?",rsOptVar="["+rsVarRange+"]?",rsOptJoin="(?:"+rsZWJ+"(?:"+[rsNonAstral,rsRegional,rsSurrPair].join("|")+")"+rsOptVar+reOptMod+")*",rsOrdLower="\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)",rsOrdUpper="\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)",rsSeq=rsOptVar+reOptMod+rsOptJoin,rsEmoji="(?:"+[rsDingbat,rsRegional,rsSurrPair].join("|")+")"+rsSeq,rsSymbol="(?:"+[rsNonAstral+rsCombo+"?",rsCombo,rsRegional,rsSurrPair,rsAstral].join("|")+")";var reApos=RegExp(rsApos,"g");var reComboMark=RegExp(rsCombo,"g");var reUnicode=RegExp(rsFitz+"(?="+rsFitz+")|"+rsSymbol+rsSeq,"g");var reUnicodeWord=RegExp([rsUpper+"?"+rsLower+"+"+rsOptContrLower+"(?="+[rsBreak,rsUpper,"$"].join("|")+")",rsMiscUpper+"+"+rsOptContrUpper+"(?="+[rsBreak,rsUpper+rsMiscLower,"$"].join("|")+")",rsUpper+"?"+rsMiscLower+"+"+rsOptContrLower,rsUpper+"+"+rsOptContrUpper,rsOrdUpper,rsOrdLower,rsDigits,rsEmoji].join("|"),"g");var reHasUnicode=RegExp("["+rsZWJ+rsAstralRange+rsComboRange+rsVarRange+"]");var reHasUnicodeWord=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;var contextProps=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"];var templateCounter=-1;var typedArrayTags={};typedArrayTags[float32Tag]=typedArrayTags[float64Tag]=typedArrayTags[int8Tag]=typedArrayTags[int16Tag]=typedArrayTags[int32Tag]=typedArrayTags[uint8Tag]=typedArrayTags[uint8ClampedTag]=typedArrayTags[uint16Tag]=typedArrayTags[uint32Tag]=true;typedArrayTags[argsTag]=typedArrayTags[arrayTag]=typedArrayTags[arrayBufferTag]=typedArrayTags[boolTag]=typedArrayTags[dataViewTag]=typedArrayTags[dateTag]=typedArrayTags[errorTag]=typedArrayTags[funcTag]=typedArrayTags[mapTag]=typedArrayTags[numberTag]=typedArrayTags[objectTag]=typedArrayTags[regexpTag]=typedArrayTags[setTag]=typedArrayTags[stringTag]=typedArrayTags[weakMapTag]=false;var cloneableTags={};cloneableTags[argsTag]=cloneableTags[arrayTag]=cloneableTags[arrayBufferTag]=cloneableTags[dataViewTag]=cloneableTags[boolTag]=cloneableTags[dateTag]=cloneableTags[float32Tag]=cloneableTags[float64Tag]=cloneableTags[int8Tag]=cloneableTags[int16Tag]=cloneableTags[int32Tag]=cloneableTags[mapTag]=cloneableTags[numberTag]=cloneableTags[objectTag]=cloneableTags[regexpTag]=cloneableTags[setTag]=cloneableTags[stringTag]=cloneableTags[symbolTag]=cloneableTags[uint8Tag]=cloneableTags[uint8ClampedTag]=cloneableTags[uint16Tag]=cloneableTags[uint32Tag]=true;cloneableTags[errorTag]=cloneableTags[funcTag]=cloneableTags[weakMapTag]=false;var deburredLetters={"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss","Ā":"A","Ă":"A","Ą":"A","ā":"a","ă":"a","ą":"a","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","ć":"c","ĉ":"c","ċ":"c","č":"c","Ď":"D","Đ":"D","ď":"d","đ":"d","Ē":"E","Ĕ":"E","Ė":"E","Ę":"E","Ě":"E","ē":"e","ĕ":"e","ė":"e","ę":"e","ě":"e","Ĝ":"G","Ğ":"G","Ġ":"G","Ģ":"G","ĝ":"g","ğ":"g","ġ":"g","ģ":"g","Ĥ":"H","Ħ":"H","ĥ":"h","ħ":"h","Ĩ":"I","Ī":"I","Ĭ":"I","Į":"I","İ":"I","ĩ":"i","ī":"i","ĭ":"i","į":"i","ı":"i","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","ĸ":"k","Ĺ":"L","Ļ":"L","Ľ":"L","Ŀ":"L","Ł":"L","ĺ":"l","ļ":"l","ľ":"l","ŀ":"l","ł":"l","Ń":"N","Ņ":"N","Ň":"N","Ŋ":"N","ń":"n","ņ":"n","ň":"n","ŋ":"n","Ō":"O","Ŏ":"O","Ő":"O","ō":"o","ŏ":"o","ő":"o","Ŕ":"R","Ŗ":"R","Ř":"R","ŕ":"r","ŗ":"r","ř":"r","Ś":"S","Ŝ":"S","Ş":"S","Š":"S","ś":"s","ŝ":"s","ş":"s","š":"s","Ţ":"T","Ť":"T","Ŧ":"T","ţ":"t","ť":"t","ŧ":"t","Ũ":"U","Ū":"U","Ŭ":"U","Ů":"U","Ű":"U","Ų":"U","ũ":"u","ū":"u","ŭ":"u","ů":"u","ű":"u","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","Ż":"Z","Ž":"Z","ź":"z","ż":"z","ž":"z","IJ":"IJ","ij":"ij","Œ":"Oe","œ":"oe","ʼn":"'n","ſ":"s"};var htmlEscapes={"&":"&","<":"<",">":">",'"':""","'":"'"};var htmlUnescapes={"&":"&","<":"<",">":">",""":'"',"'":"'"};var stringEscapes={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"};var freeParseFloat=parseFloat,freeParseInt=parseInt;var freeGlobal=typeof global=="object"&&global&&global.Object===Object&&global;var freeSelf=typeof self=="object"&&self&&self.Object===Object&&self;var root=freeGlobal||freeSelf||Function("return this")();var freeExports=typeof exports=="object"&&exports&&!exports.nodeType&&exports;var freeModule=freeExports&&typeof module=="object"&&module&&!module.nodeType&&module;var moduleExports=freeModule&&freeModule.exports===freeExports;var freeProcess=moduleExports&&freeGlobal.process;var nodeUtil=function(){try{return freeProcess&&freeProcess.binding&&freeProcess.binding("util")}catch(e){}}();var nodeIsArrayBuffer=nodeUtil&&nodeUtil.isArrayBuffer,nodeIsDate=nodeUtil&&nodeUtil.isDate,nodeIsMap=nodeUtil&&nodeUtil.isMap,nodeIsRegExp=nodeUtil&&nodeUtil.isRegExp,nodeIsSet=nodeUtil&&nodeUtil.isSet,nodeIsTypedArray=nodeUtil&&nodeUtil.isTypedArray;function addMapEntry(map,pair){map.set(pair[0],pair[1]);return map}function addSetEntry(set,value){set.add(value);return set}function apply(func,thisArg,args){switch(args.length){case 0:return func.call(thisArg);case 1:return func.call(thisArg,args[0]);case 2:return func.call(thisArg,args[0],args[1]);case 3:return func.call(thisArg,args[0],args[1],args[2])}return func.apply(thisArg,args)}function arrayAggregator(array,setter,iteratee,accumulator){var index=-1,length=array==null?0:array.length;while(++index-1}function arrayIncludesWith(array,value,comparator){var index=-1,length=array==null?0:array.length;while(++index-1){}return index}function charsEndIndex(strSymbols,chrSymbols){var index=strSymbols.length;while(index--&&baseIndexOf(chrSymbols,strSymbols[index],0)>-1){}return index}function countHolders(array,placeholder){var length=array.length,result=0;while(length--){if(array[length]===placeholder){++result}}return result}var deburrLetter=basePropertyOf(deburredLetters);var escapeHtmlChar=basePropertyOf(htmlEscapes);function escapeStringChar(chr){return"\\"+stringEscapes[chr]}function getValue(object,key){return object==null?undefined:object[key]}function hasUnicode(string){return reHasUnicode.test(string)}function hasUnicodeWord(string){return reHasUnicodeWord.test(string)}function iteratorToArray(iterator){var data,result=[];while(!(data=iterator.next()).done){result.push(data.value)}return result}function mapToArray(map){var index=-1,result=Array(map.size);map.forEach(function(value,key){result[++index]=[key,value]});return result}function overArg(func,transform){return function(arg){return func(transform(arg))}}function replaceHolders(array,placeholder){var index=-1,length=array.length,resIndex=0,result=[];while(++index-1}function listCacheSet(key,value){var data=this.__data__,index=assocIndexOf(data,key);if(index<0){++this.size;data.push([key,value])}else{data[index][1]=value}return this}ListCache.prototype.clear=listCacheClear;ListCache.prototype["delete"]=listCacheDelete;ListCache.prototype.get=listCacheGet;ListCache.prototype.has=listCacheHas;ListCache.prototype.set=listCacheSet;function MapCache(entries){var index=-1,length=entries==null?0:entries.length;this.clear();while(++index=lower?number:lower}}return number}function baseClone(value,bitmask,customizer,key,object,stack){var result,isDeep=bitmask&CLONE_DEEP_FLAG,isFlat=bitmask&CLONE_FLAT_FLAG,isFull=bitmask&CLONE_SYMBOLS_FLAG;if(customizer){result=object?customizer(value,key,object,stack):customizer(value)}if(result!==undefined){return result}if(!isObject(value)){return value}var isArr=isArray(value);if(isArr){result=initCloneArray(value);if(!isDeep){return copyArray(value,result)}}else{var tag=getTag(value),isFunc=tag==funcTag||tag==genTag;if(isBuffer(value)){return cloneBuffer(value,isDeep)}if(tag==objectTag||tag==argsTag||isFunc&&!object){result=isFlat||isFunc?{}:initCloneObject(value);if(!isDeep){return isFlat?copySymbolsIn(value,baseAssignIn(result,value)):copySymbols(value,baseAssign(result,value))}}else{if(!cloneableTags[tag]){return object?value:{}}result=initCloneByTag(value,tag,baseClone,isDeep)}}stack||(stack=new Stack);var stacked=stack.get(value);if(stacked){return stacked}stack.set(value,result);var keysFunc=isFull?isFlat?getAllKeysIn:getAllKeys:isFlat?keysIn:keys;var props=isArr?undefined:keysFunc(value);arrayEach(props||value,function(subValue,key){if(props){key=subValue;subValue=value[key]}assignValue(result,key,baseClone(subValue,bitmask,customizer,key,value,stack))});return result}function baseConforms(source){var props=keys(source);return function(object){return baseConformsTo(object,source,props)}}function baseConformsTo(object,source,props){var length=props.length;if(object==null){return!length}object=Object(object);while(length--){var key=props[length],predicate=source[key],value=object[key];if(value===undefined&&!(key in object)||!predicate(value)){return false}}return true}function baseDelay(func,wait,args){if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}return setTimeout(function(){func.apply(undefined,args)},wait)}function baseDifference(array,values,iteratee,comparator){var index=-1,includes=arrayIncludes,isCommon=true,length=array.length,result=[],valuesLength=values.length;if(!length){return result}if(iteratee){values=arrayMap(values,baseUnary(iteratee))}if(comparator){includes=arrayIncludesWith;isCommon=false}else if(values.length>=LARGE_ARRAY_SIZE){includes=cacheHas;isCommon=false;values=new SetCache(values)}outer:while(++indexlength?0:length+start}end=end===undefined||end>length?length:toInteger(end);if(end<0){end+=length}end=start>end?0:toLength(end);while(start0&&predicate(value)){if(depth>1){baseFlatten(value,depth-1,predicate,isStrict,result)}else{arrayPush(result,value)}}else if(!isStrict){result[result.length]=value}}return result}var baseFor=createBaseFor();var baseForRight=createBaseFor(true);function baseForOwn(object,iteratee){return object&&baseFor(object,iteratee,keys)}function baseForOwnRight(object,iteratee){return object&&baseForRight(object,iteratee,keys)}function baseFunctions(object,props){return arrayFilter(props,function(key){return isFunction(object[key])})}function baseGet(object,path){path=castPath(path,object);var index=0,length=path.length;while(object!=null&&indexother}function baseHas(object,key){return object!=null&&hasOwnProperty.call(object,key)}function baseHasIn(object,key){return object!=null&&key in Object(object)}function baseInRange(number,start,end){return number>=nativeMin(start,end)&&number=120&&array.length>=120)?new SetCache(othIndex&&array):undefined}array=arrays[0];var index=-1,seen=caches[0];outer:while(++index-1){if(seen!==array){splice.call(seen,fromIndex,1)}splice.call(array,fromIndex,1)}}return array}function basePullAt(array,indexes){var length=array?indexes.length:0,lastIndex=length-1;while(length--){var index=indexes[length];if(length==lastIndex||index!==previous){var previous=index;if(isIndex(index)){splice.call(array,index,1)}else{baseUnset(array,index)}}}return array}function baseRandom(lower,upper){return lower+nativeFloor(nativeRandom()*(upper-lower+1))}function baseRange(start,end,step,fromRight){var index=-1,length=nativeMax(nativeCeil((end-start)/(step||1)),0),result=Array(length);while(length--){result[fromRight?length:++index]=start;start+=step}return result}function baseRepeat(string,n){var result="";if(!string||n<1||n>MAX_SAFE_INTEGER){return result}do{if(n%2){result+=string}n=nativeFloor(n/2);if(n){string+=string}}while(n)return result}function baseRest(func,start){return setToString(overRest(func,start,identity),func+"")}function baseSample(collection){return arraySample(values(collection))}function baseSampleSize(collection,n){var array=values(collection);return shuffleSelf(array,baseClamp(n,0,array.length))}function baseSet(object,path,value,customizer){if(!isObject(object)){return object}path=castPath(path,object);var index=-1,length=path.length,lastIndex=length-1,nested=object;while(nested!=null&&++indexlength?0:length+start}end=end>length?length:end;if(end<0){end+=length}length=start>end?0:end-start>>>0;start>>>=0;var result=Array(length);while(++index>>1,computed=array[mid];if(computed!==null&&!isSymbol(computed)&&(retHighest?computed<=value:computed=LARGE_ARRAY_SIZE){var set=iteratee?null:createSet(array);if(set){return setToArray(set)}isCommon=false;includes=cacheHas;seen=new SetCache}else{seen=iteratee?[]:result}outer:while(++index=length?array:baseSlice(array,start,end)}var clearTimeout=ctxClearTimeout||function(id){return root.clearTimeout(id)};function cloneBuffer(buffer,isDeep){if(isDeep){return buffer.slice()}var length=buffer.length,result=allocUnsafe?allocUnsafe(length):new buffer.constructor(length);buffer.copy(result);return result}function cloneArrayBuffer(arrayBuffer){var result=new arrayBuffer.constructor(arrayBuffer.byteLength);new Uint8Array(result).set(new Uint8Array(arrayBuffer));return result}function cloneDataView(dataView,isDeep){var buffer=isDeep?cloneArrayBuffer(dataView.buffer):dataView.buffer;return new dataView.constructor(buffer,dataView.byteOffset,dataView.byteLength)}function cloneMap(map,isDeep,cloneFunc){var array=isDeep?cloneFunc(mapToArray(map),CLONE_DEEP_FLAG):mapToArray(map);return arrayReduce(array,addMapEntry,new map.constructor)}function cloneRegExp(regexp){var result=new regexp.constructor(regexp.source,reFlags.exec(regexp));result.lastIndex=regexp.lastIndex;return result}function cloneSet(set,isDeep,cloneFunc){var array=isDeep?cloneFunc(setToArray(set),CLONE_DEEP_FLAG):setToArray(set);return arrayReduce(array,addSetEntry,new set.constructor)}function cloneSymbol(symbol){return symbolValueOf?Object(symbolValueOf.call(symbol)):{}}function cloneTypedArray(typedArray,isDeep){var buffer=isDeep?cloneArrayBuffer(typedArray.buffer):typedArray.buffer;return new typedArray.constructor(buffer,typedArray.byteOffset,typedArray.length)}function compareAscending(value,other){if(value!==other){var valIsDefined=value!==undefined,valIsNull=value===null,valIsReflexive=value===value,valIsSymbol=isSymbol(value);var othIsDefined=other!==undefined,othIsNull=other===null,othIsReflexive=other===other,othIsSymbol=isSymbol(other);if(!othIsNull&&!othIsSymbol&&!valIsSymbol&&value>other||valIsSymbol&&othIsDefined&&othIsReflexive&&!othIsNull&&!othIsSymbol||valIsNull&&othIsDefined&&othIsReflexive||!valIsDefined&&othIsReflexive||!valIsReflexive){return 1}if(!valIsNull&&!valIsSymbol&&!othIsSymbol&&value=ordersLength){return result}var order=orders[index];return result*(order=="desc"?-1:1)}}return object.index-other.index}function composeArgs(args,partials,holders,isCurried){var argsIndex=-1,argsLength=args.length,holdersLength=holders.length,leftIndex=-1,leftLength=partials.length,rangeLength=nativeMax(argsLength-holdersLength,0),result=Array(leftLength+rangeLength),isUncurried=!isCurried;while(++leftIndex1?sources[length-1]:undefined,guard=length>2?sources[2]:undefined;customizer=assigner.length>3&&typeof customizer=="function"?(length--,customizer):undefined;if(guard&&isIterateeCall(sources[0],sources[1],guard)){customizer=length<3?undefined:customizer;length=1}object=Object(object);while(++index-1?iterable[iteratee?collection[index]:index]:undefined}}function createFlow(fromRight){return flatRest(function(funcs){var length=funcs.length,index=length,prereq=LodashWrapper.prototype.thru;if(fromRight){funcs.reverse()}while(index--){var func=funcs[index];if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}if(prereq&&!wrapper&&getFuncName(func)=="wrapper"){var wrapper=new LodashWrapper([],true)}}index=wrapper?index:length;while(++index1){args.reverse()}if(isAry&&aryarrLength)){return false}var stacked=stack.get(array);if(stacked&&stack.get(other)){return stacked==other}var index=-1,result=true,seen=bitmask&COMPARE_UNORDERED_FLAG?new SetCache:undefined;stack.set(array,other);stack.set(other,array);while(++index1?"& ":"")+details[lastIndex];details=details.join(length>2?", ":" ");return source.replace(reWrapComment,"{\n/* [wrapped with "+details+"] */\n")}function isFlattenable(value){return isArray(value)||isArguments(value)||!!(spreadableSymbol&&value&&value[spreadableSymbol])}function isIndex(value,length){length=length==null?MAX_SAFE_INTEGER:length;return!!length&&(typeof value=="number"||reIsUint.test(value))&&(value>-1&&value%1==0&&value0){if(++count>=HOT_COUNT){return arguments[0]}}else{count=0}return func.apply(undefined,arguments)}}function shuffleSelf(array,size){var index=-1,length=array.length,lastIndex=length-1;size=size===undefined?length:size;while(++index1?arrays[length-1]:undefined;iteratee=typeof iteratee=="function"?(arrays.pop(),iteratee):undefined;return unzipWith(arrays,iteratee)});function chain(value){var result=lodash(value);result.__chain__=true;return result}function tap(value,interceptor){interceptor(value);return value}function thru(value,interceptor){return interceptor(value)}var wrapperAt=flatRest(function(paths){var length=paths.length,start=length?paths[0]:0,value=this.__wrapped__,interceptor=function(object){return baseAt(object,paths)};if(length>1||this.__actions__.length||!(value instanceof LazyWrapper)||!isIndex(start)){return this.thru(interceptor)}value=value.slice(start,+start+(length?1:0));value.__actions__.push({func:thru,args:[interceptor],thisArg:undefined});return new LodashWrapper(value,this.__chain__).thru(function(array){if(length&&!array.length){array.push(undefined)}return array})});function wrapperChain(){return chain(this)}function wrapperCommit(){return new LodashWrapper(this.value(),this.__chain__)}function wrapperNext(){if(this.__values__===undefined){this.__values__=toArray(this.value())}var done=this.__index__>=this.__values__.length,value=done?undefined:this.__values__[this.__index__++];return{done:done,value:value}}function wrapperToIterator(){return this}function wrapperPlant(value){var result,parent=this;while(parent instanceof baseLodash){var clone=wrapperClone(parent);clone.__index__=0;clone.__values__=undefined;if(result){previous.__wrapped__=clone}else{result=clone}var previous=clone;parent=parent.__wrapped__}previous.__wrapped__=value;return result}function wrapperReverse(){var value=this.__wrapped__;if(value instanceof LazyWrapper){var wrapped=value;if(this.__actions__.length){wrapped=new LazyWrapper(this)}wrapped=wrapped.reverse();wrapped.__actions__.push({func:thru,args:[reverse],thisArg:undefined});return new LodashWrapper(wrapped,this.__chain__)}return this.thru(reverse)}function wrapperValue(){return baseWrapperValue(this.__wrapped__,this.__actions__)}var countBy=createAggregator(function(result,value,key){if(hasOwnProperty.call(result,key)){++result[key]}else{baseAssignValue(result,key,1)}});function every(collection,predicate,guard){var func=isArray(collection)?arrayEvery:baseEvery;if(guard&&isIterateeCall(collection,predicate,guard)){predicate=undefined}return func(collection,getIteratee(predicate,3))}function filter(collection,predicate){var func=isArray(collection)?arrayFilter:baseFilter;return func(collection,getIteratee(predicate,3))}var find=createFind(findIndex);var findLast=createFind(findLastIndex);function flatMap(collection,iteratee){return baseFlatten(map(collection,iteratee),1)}function flatMapDeep(collection,iteratee){return baseFlatten(map(collection,iteratee),INFINITY)}function flatMapDepth(collection,iteratee,depth){depth=depth===undefined?1:toInteger(depth);return baseFlatten(map(collection,iteratee),depth)}function forEach(collection,iteratee){var func=isArray(collection)?arrayEach:baseEach;return func(collection,getIteratee(iteratee,3))}function forEachRight(collection,iteratee){var func=isArray(collection)?arrayEachRight:baseEachRight;return func(collection,getIteratee(iteratee,3))}var groupBy=createAggregator(function(result,value,key){if(hasOwnProperty.call(result,key)){result[key].push(value)}else{baseAssignValue(result,key,[value])}});function includes(collection,value,fromIndex,guard){collection=isArrayLike(collection)?collection:values(collection);fromIndex=fromIndex&&!guard?toInteger(fromIndex):0;var length=collection.length;if(fromIndex<0){fromIndex=nativeMax(length+fromIndex,0)}return isString(collection)?fromIndex<=length&&collection.indexOf(value,fromIndex)>-1:!!length&&baseIndexOf(collection,value,fromIndex)>-1}var invokeMap=baseRest(function(collection,path,args){var index=-1,isFunc=typeof path=="function",result=isArrayLike(collection)?Array(collection.length):[];baseEach(collection,function(value){result[++index]=isFunc?apply(path,value,args):baseInvoke(value,path,args)});return result});var keyBy=createAggregator(function(result,value,key){baseAssignValue(result,key,value)});function map(collection,iteratee){var func=isArray(collection)?arrayMap:baseMap;return func(collection,getIteratee(iteratee,3))}function orderBy(collection,iteratees,orders,guard){if(collection==null){return[]}if(!isArray(iteratees)){iteratees=iteratees==null?[]:[iteratees]}orders=guard?undefined:orders;if(!isArray(orders)){orders=orders==null?[]:[orders]}return baseOrderBy(collection,iteratees,orders)}var partition=createAggregator(function(result,value,key){result[key?0:1].push(value)},function(){return[[],[]]});function reduce(collection,iteratee,accumulator){var func=isArray(collection)?arrayReduce:baseReduce,initAccum=arguments.length<3;return func(collection,getIteratee(iteratee,4),accumulator,initAccum,baseEach)}function reduceRight(collection,iteratee,accumulator){var func=isArray(collection)?arrayReduceRight:baseReduce,initAccum=arguments.length<3;return func(collection,getIteratee(iteratee,4),accumulator,initAccum,baseEachRight)}function reject(collection,predicate){var func=isArray(collection)?arrayFilter:baseFilter;return func(collection,negate(getIteratee(predicate,3)))}function sample(collection){var func=isArray(collection)?arraySample:baseSample;return func(collection)}function sampleSize(collection,n,guard){if(guard?isIterateeCall(collection,n,guard):n===undefined){n=1}else{n=toInteger(n)}var func=isArray(collection)?arraySampleSize:baseSampleSize;return func(collection,n)}function shuffle(collection){var func=isArray(collection)?arrayShuffle:baseShuffle;return func(collection)}function size(collection){if(collection==null){return 0}if(isArrayLike(collection)){return isString(collection)?stringSize(collection):collection.length}var tag=getTag(collection);if(tag==mapTag||tag==setTag){return collection.size}return baseKeys(collection).length}function some(collection,predicate,guard){var func=isArray(collection)?arraySome:baseSome;if(guard&&isIterateeCall(collection,predicate,guard)){predicate=undefined}return func(collection,getIteratee(predicate,3))}var sortBy=baseRest(function(collection,iteratees){if(collection==null){return[]}var length=iteratees.length;if(length>1&&isIterateeCall(collection,iteratees[0],iteratees[1])){iteratees=[]}else if(length>2&&isIterateeCall(iteratees[0],iteratees[1],iteratees[2])){iteratees=[iteratees[0]]}return baseOrderBy(collection,baseFlatten(iteratees,1),[])});var now=ctxNow||function(){return root.Date.now()};function after(n,func){if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}n=toInteger(n);return function(){if(--n<1){return func.apply(this,arguments)}}}function ary(func,n,guard){n=guard?undefined:n;n=func&&n==null?func.length:n;return createWrap(func,WRAP_ARY_FLAG,undefined,undefined,undefined,undefined,n)}function before(n,func){var result;if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}n=toInteger(n);return function(){if(--n>0){result=func.apply(this,arguments)}if(n<=1){func=undefined}return result}}var bind=baseRest(function(func,thisArg,partials){var bitmask=WRAP_BIND_FLAG;if(partials.length){var holders=replaceHolders(partials,getHolder(bind));bitmask|=WRAP_PARTIAL_FLAG}return createWrap(func,bitmask,thisArg,partials,holders)});var bindKey=baseRest(function(object,key,partials){var bitmask=WRAP_BIND_FLAG|WRAP_BIND_KEY_FLAG;if(partials.length){var holders=replaceHolders(partials,getHolder(bindKey));bitmask|=WRAP_PARTIAL_FLAG}return createWrap(key,bitmask,object,partials,holders)});function curry(func,arity,guard){arity=guard?undefined:arity;var result=createWrap(func,WRAP_CURRY_FLAG,undefined,undefined,undefined,undefined,undefined,arity);result.placeholder=curry.placeholder;return result}function curryRight(func,arity,guard){arity=guard?undefined:arity;var result=createWrap(func,WRAP_CURRY_RIGHT_FLAG,undefined,undefined,undefined,undefined,undefined,arity);result.placeholder=curryRight.placeholder;return result}function debounce(func,wait,options){var lastArgs,lastThis,maxWait,result,timerId,lastCallTime,lastInvokeTime=0,leading=false,maxing=false,trailing=true;if(typeof func!="function"){throw new TypeError(FUNC_ERROR_TEXT)}wait=toNumber(wait)||0;if(isObject(options)){leading=!!options.leading;maxing="maxWait"in options;maxWait=maxing?nativeMax(toNumber(options.maxWait)||0,wait):maxWait;trailing="trailing"in options?!!options.trailing:trailing}function invokeFunc(time){var args=lastArgs,thisArg=lastThis;lastArgs=lastThis=undefined;lastInvokeTime=time;result=func.apply(thisArg,args);return result}function leadingEdge(time){lastInvokeTime=time;timerId=setTimeout(timerExpired,wait);return leading?invokeFunc(time):result}function remainingWait(time){var timeSinceLastCall=time-lastCallTime,timeSinceLastInvoke=time-lastInvokeTime,result=wait-timeSinceLastCall;return maxing?nativeMin(result,maxWait-timeSinceLastInvoke):result}function shouldInvoke(time){var timeSinceLastCall=time-lastCallTime,timeSinceLastInvoke=time-lastInvokeTime;return lastCallTime===undefined||timeSinceLastCall>=wait||timeSinceLastCall<0||maxing&&timeSinceLastInvoke>=maxWait}function timerExpired(){var time=now();if(shouldInvoke(time)){return trailingEdge(time)}timerId=setTimeout(timerExpired,remainingWait(time))}function trailingEdge(time){timerId=undefined;if(trailing&&lastArgs){return invokeFunc(time)}lastArgs=lastThis=undefined;return result}function cancel(){if(timerId!==undefined){clearTimeout(timerId)}lastInvokeTime=0;lastArgs=lastCallTime=lastThis=timerId=undefined}function flush(){return timerId===undefined?result:trailingEdge(now())}function debounced(){var time=now(),isInvoking=shouldInvoke(time);lastArgs=arguments;lastThis=this;lastCallTime=time;if(isInvoking){if(timerId===undefined){return leadingEdge(lastCallTime)}if(maxing){timerId=setTimeout(timerExpired,wait);return invokeFunc(lastCallTime)}}if(timerId===undefined){timerId=setTimeout(timerExpired,wait)}return result}debounced.cancel=cancel;debounced.flush=flush;return debounced}var defer=baseRest(function(func,args){return baseDelay(func,1,args)});var delay=baseRest(function(func,wait,args){return baseDelay(func,toNumber(wait)||0,args)});function flip(func){return createWrap(func,WRAP_FLIP_FLAG)}function memoize(func,resolver){if(typeof func!="function"||resolver!=null&&typeof resolver!="function"){throw new TypeError(FUNC_ERROR_TEXT)}var memoized=function(){var args=arguments,key=resolver?resolver.apply(this,args):args[0],cache=memoized.cache;if(cache.has(key)){return cache.get(key)}var result=func.apply(this,args);memoized.cache=cache.set(key,result)||cache;return result};memoized.cache=new(memoize.Cache||MapCache);return memoized}memoize.Cache=MapCache;function negate(predicate){if(typeof predicate!="function"){throw new TypeError(FUNC_ERROR_TEXT)}return function(){var args=arguments;switch(args.length){case 0:return!predicate.call(this);case 1:return!predicate.call(this,args[0]);case 2:return!predicate.call(this,args[0],args[1]);case 3:return!predicate.call(this,args[0],args[1],args[2])}return!predicate.apply(this,args)}}function once(func){return before(2,func)}var overArgs=castRest(function(func,transforms){transforms=transforms.length==1&&isArray(transforms[0])?arrayMap(transforms[0],baseUnary(getIteratee())):arrayMap(baseFlatten(transforms,1),baseUnary(getIteratee()));var funcsLength=transforms.length;return baseRest(function(args){var index=-1,length=nativeMin(args.length,funcsLength);while(++index=other});var isArguments=baseIsArguments(function(){return arguments}())?baseIsArguments:function(value){return isObjectLike(value)&&hasOwnProperty.call(value,"callee")&&!propertyIsEnumerable.call(value,"callee")};var isArray=Array.isArray;var isArrayBuffer=nodeIsArrayBuffer?baseUnary(nodeIsArrayBuffer):baseIsArrayBuffer;function isArrayLike(value){return value!=null&&isLength(value.length)&&!isFunction(value)}function isArrayLikeObject(value){return isObjectLike(value)&&isArrayLike(value)}function isBoolean(value){return value===true||value===false||isObjectLike(value)&&baseGetTag(value)==boolTag}var isBuffer=nativeIsBuffer||stubFalse;var isDate=nodeIsDate?baseUnary(nodeIsDate):baseIsDate;function isElement(value){return isObjectLike(value)&&value.nodeType===1&&!isPlainObject(value)}function isEmpty(value){if(value==null){return true}if(isArrayLike(value)&&(isArray(value)||typeof value=="string"||typeof value.splice=="function"||isBuffer(value)||isTypedArray(value)||isArguments(value))){return!value.length}var tag=getTag(value);if(tag==mapTag||tag==setTag){return!value.size}if(isPrototype(value)){return!baseKeys(value).length}for(var key in value){if(hasOwnProperty.call(value,key)){return false}}return true}function isEqual(value,other){return baseIsEqual(value,other)}function isEqualWith(value,other,customizer){customizer=typeof customizer=="function"?customizer:undefined;var result=customizer?customizer(value,other):undefined;return result===undefined?baseIsEqual(value,other,undefined,customizer):!!result}function isError(value){if(!isObjectLike(value)){return false}var tag=baseGetTag(value);return tag==errorTag||tag==domExcTag||typeof value.message=="string"&&typeof value.name=="string"&&!isPlainObject(value)}function isFinite(value){return typeof value=="number"&&nativeIsFinite(value)}function isFunction(value){if(!isObject(value)){return false}var tag=baseGetTag(value);return tag==funcTag||tag==genTag||tag==asyncTag||tag==proxyTag}function isInteger(value){return typeof value=="number"&&value==toInteger(value)}function isLength(value){return typeof value=="number"&&value>-1&&value%1==0&&value<=MAX_SAFE_INTEGER}function isObject(value){var type=typeof value;return value!=null&&(type=="object"||type=="function")}function isObjectLike(value){return value!=null&&typeof value=="object"}var isMap=nodeIsMap?baseUnary(nodeIsMap):baseIsMap;function isMatch(object,source){return object===source||baseIsMatch(object,source,getMatchData(source))}function isMatchWith(object,source,customizer){customizer=typeof customizer=="function"?customizer:undefined;return baseIsMatch(object,source,getMatchData(source),customizer)}function isNaN(value){return isNumber(value)&&value!=+value}function isNative(value){if(isMaskable(value)){throw new Error(CORE_ERROR_TEXT)}return baseIsNative(value)}function isNull(value){return value===null}function isNil(value){return value==null}function isNumber(value){return typeof value=="number"||isObjectLike(value)&&baseGetTag(value)==numberTag}function isPlainObject(value){if(!isObjectLike(value)||baseGetTag(value)!=objectTag){return false}var proto=getPrototype(value);if(proto===null){return true}var Ctor=hasOwnProperty.call(proto,"constructor")&&proto.constructor;return typeof Ctor=="function"&&Ctor instanceof Ctor&&funcToString.call(Ctor)==objectCtorString}var isRegExp=nodeIsRegExp?baseUnary(nodeIsRegExp):baseIsRegExp;function isSafeInteger(value){return isInteger(value)&&value>=-MAX_SAFE_INTEGER&&value<=MAX_SAFE_INTEGER}var isSet=nodeIsSet?baseUnary(nodeIsSet):baseIsSet;function isString(value){return typeof value=="string"||!isArray(value)&&isObjectLike(value)&&baseGetTag(value)==stringTag}function isSymbol(value){return typeof value=="symbol"||isObjectLike(value)&&baseGetTag(value)==symbolTag}var isTypedArray=nodeIsTypedArray?baseUnary(nodeIsTypedArray):baseIsTypedArray;function isUndefined(value){return value===undefined}function isWeakMap(value){return isObjectLike(value)&&getTag(value)==weakMapTag}function isWeakSet(value){return isObjectLike(value)&&baseGetTag(value)==weakSetTag}var lt=createRelationalOperation(baseLt);var lte=createRelationalOperation(function(value,other){return value<=other});function toArray(value){if(!value){return[]}if(isArrayLike(value)){return isString(value)?stringToArray(value):copyArray(value)}if(symIterator&&value[symIterator]){return iteratorToArray(value[symIterator]())}var tag=getTag(value),func=tag==mapTag?mapToArray:tag==setTag?setToArray:values;return func(value)}function toFinite(value){if(!value){return value===0?value:0}value=toNumber(value);if(value===INFINITY||value===-INFINITY){var sign=value<0?-1:1;return sign*MAX_INTEGER}return value===value?value:0}function toInteger(value){var result=toFinite(value),remainder=result%1;return result===result?remainder?result-remainder:result:0}function toLength(value){return value?baseClamp(toInteger(value),0,MAX_ARRAY_LENGTH):0}function toNumber(value){if(typeof value=="number"){return value}if(isSymbol(value)){return NAN}if(isObject(value)){var other=typeof value.valueOf=="function"?value.valueOf():value;value=isObject(other)?other+"":other}if(typeof value!="string"){return value===0?value:+value}value=value.replace(reTrim,"");var isBinary=reIsBinary.test(value);return isBinary||reIsOctal.test(value)?freeParseInt(value.slice(2),isBinary?2:8):reIsBadHex.test(value)?NAN:+value}function toPlainObject(value){return copyObject(value,keysIn(value))}function toSafeInteger(value){return value?baseClamp(toInteger(value),-MAX_SAFE_INTEGER,MAX_SAFE_INTEGER):value===0?value:0}function toString(value){return value==null?"":baseToString(value)}var assign=createAssigner(function(object,source){if(isPrototype(source)||isArrayLike(source)){copyObject(source,keys(source),object);return}for(var key in source){if(hasOwnProperty.call(source,key)){assignValue(object,key,source[key])}}});var assignIn=createAssigner(function(object,source){copyObject(source,keysIn(source),object)});var assignInWith=createAssigner(function(object,source,srcIndex,customizer){copyObject(source,keysIn(source),object,customizer)});var assignWith=createAssigner(function(object,source,srcIndex,customizer){copyObject(source,keys(source),object,customizer)});var at=flatRest(baseAt);function create(prototype,properties){var result=baseCreate(prototype);return properties==null?result:baseAssign(result,properties)}var defaults=baseRest(function(args){args.push(undefined,customDefaultsAssignIn);return apply(assignInWith,undefined,args)});var defaultsDeep=baseRest(function(args){args.push(undefined,customDefaultsMerge);return apply(mergeWith,undefined,args)});function findKey(object,predicate){return baseFindKey(object,getIteratee(predicate,3),baseForOwn)}function findLastKey(object,predicate){return baseFindKey(object,getIteratee(predicate,3),baseForOwnRight)}function forIn(object,iteratee){return object==null?object:baseFor(object,getIteratee(iteratee,3),keysIn)}function forInRight(object,iteratee){return object==null?object:baseForRight(object,getIteratee(iteratee,3),keysIn)}function forOwn(object,iteratee){return object&&baseForOwn(object,getIteratee(iteratee,3))}function forOwnRight(object,iteratee){return object&&baseForOwnRight(object,getIteratee(iteratee,3))}function functions(object){return object==null?[]:baseFunctions(object,keys(object))}function functionsIn(object){return object==null?[]:baseFunctions(object,keysIn(object))}function get(object,path,defaultValue){var result=object==null?undefined:baseGet(object,path);return result===undefined?defaultValue:result}function has(object,path){return object!=null&&hasPath(object,path,baseHas)}function hasIn(object,path){return object!=null&&hasPath(object,path,baseHasIn)}var invert=createInverter(function(result,value,key){result[value]=key},constant(identity));var invertBy=createInverter(function(result,value,key){if(hasOwnProperty.call(result,value)){result[value].push(key)}else{result[value]=[key]}},getIteratee);var invoke=baseRest(baseInvoke);function keys(object){return isArrayLike(object)?arrayLikeKeys(object):baseKeys(object)}function keysIn(object){return isArrayLike(object)?arrayLikeKeys(object,true):baseKeysIn(object)}function mapKeys(object,iteratee){var result={};iteratee=getIteratee(iteratee,3);baseForOwn(object,function(value,key,object){baseAssignValue(result,iteratee(value,key,object),value)});return result}function mapValues(object,iteratee){var result={};iteratee=getIteratee(iteratee,3);baseForOwn(object,function(value,key,object){baseAssignValue(result,key,iteratee(value,key,object))});return result}var merge=createAssigner(function(object,source,srcIndex){baseMerge(object,source,srcIndex)});var mergeWith=createAssigner(function(object,source,srcIndex,customizer){baseMerge(object,source,srcIndex,customizer)});var omit=flatRest(function(object,paths){var result={};if(object==null){return result}var isDeep=false;paths=arrayMap(paths,function(path){path=castPath(path,object);isDeep||(isDeep=path.length>1);return path});copyObject(object,getAllKeysIn(object),result);if(isDeep){result=baseClone(result,CLONE_DEEP_FLAG|CLONE_FLAT_FLAG|CLONE_SYMBOLS_FLAG,customOmitClone)}var length=paths.length;while(length--){baseUnset(result,paths[length])}return result});function omitBy(object,predicate){return pickBy(object,negate(getIteratee(predicate)))}var pick=flatRest(function(object,paths){return object==null?{}:basePick(object,paths)});function pickBy(object,predicate){if(object==null){return{}}var props=arrayMap(getAllKeysIn(object),function(prop){return[prop]});predicate=getIteratee(predicate);return basePickBy(object,props,function(value,path){return predicate(value,path[0])})}function result(object,path,defaultValue){path=castPath(path,object);var index=-1,length=path.length;if(!length){length=1;object=undefined}while(++indexupper){var temp=lower;lower=upper;upper=temp}if(floating||lower%1||upper%1){var rand=nativeRandom();return nativeMin(lower+rand*(upper-lower+freeParseFloat("1e-"+((rand+"").length-1))),upper)}return baseRandom(lower,upper)}var camelCase=createCompounder(function(result,word,index){word=word.toLowerCase();return result+(index?capitalize(word):word)});function capitalize(string){return upperFirst(toString(string).toLowerCase())}function deburr(string){string=toString(string);return string&&string.replace(reLatin,deburrLetter).replace(reComboMark,"")}function endsWith(string,target,position){string=toString(string);target=baseToString(target);var length=string.length;position=position===undefined?length:baseClamp(toInteger(position),0,length);var end=position;position-=target.length;return position>=0&&string.slice(position,end)==target}function escape(string){string=toString(string);return string&&reHasUnescapedHtml.test(string)?string.replace(reUnescapedHtml,escapeHtmlChar):string}function escapeRegExp(string){string=toString(string);return string&&reHasRegExpChar.test(string)?string.replace(reRegExpChar,"\\$&"):string}var kebabCase=createCompounder(function(result,word,index){return result+(index?"-":"")+word.toLowerCase()});var lowerCase=createCompounder(function(result,word,index){return result+(index?" ":"")+word.toLowerCase()});var lowerFirst=createCaseFirst("toLowerCase");function pad(string,length,chars){string=toString(string);length=toInteger(length);var strLength=length?stringSize(string):0;if(!length||strLength>=length){return string}var mid=(length-strLength)/2;return createPadding(nativeFloor(mid),chars)+string+createPadding(nativeCeil(mid),chars)}function padEnd(string,length,chars){string=toString(string);length=toInteger(length);var strLength=length?stringSize(string):0;return length&&strLength>>0;if(!limit){return[]}string=toString(string);if(string&&(typeof separator=="string"||separator!=null&&!isRegExp(separator))){separator=baseToString(separator);if(!separator&&hasUnicode(string)){return castSlice(stringToArray(string),0,limit)}}return string.split(separator,limit)}var startCase=createCompounder(function(result,word,index){return result+(index?" ":"")+upperFirst(word)});function startsWith(string,target,position){string=toString(string);position=position==null?0:baseClamp(toInteger(position),0,string.length);target=baseToString(target);return string.slice(position,position+target.length)==target}function template(string,options,guard){var settings=lodash.templateSettings;if(guard&&isIterateeCall(string,options,guard)){options=undefined}string=toString(string);options=assignInWith({},options,settings,customDefaultsAssignIn);var imports=assignInWith({},options.imports,settings.imports,customDefaultsAssignIn),importsKeys=keys(imports),importsValues=baseValues(imports,importsKeys);var isEscaping,isEvaluating,index=0,interpolate=options.interpolate||reNoMatch,source="__p += '";var reDelimiters=RegExp((options.escape||reNoMatch).source+"|"+interpolate.source+"|"+(interpolate===reInterpolate?reEsTemplate:reNoMatch).source+"|"+(options.evaluate||reNoMatch).source+"|$","g");var sourceURL="//# sourceURL="+("sourceURL"in options?options.sourceURL:"lodash.templateSources["+ ++templateCounter+"]")+"\n";string.replace(reDelimiters,function(match,escapeValue,interpolateValue,esTemplateValue,evaluateValue,offset){ 5 | interpolateValue||(interpolateValue=esTemplateValue);source+=string.slice(index,offset).replace(reUnescapedString,escapeStringChar);if(escapeValue){isEscaping=true;source+="' +\n__e("+escapeValue+") +\n'"}if(evaluateValue){isEvaluating=true;source+="';\n"+evaluateValue+";\n__p += '"}if(interpolateValue){source+="' +\n((__t = ("+interpolateValue+")) == null ? '' : __t) +\n'"}index=offset+match.length;return match});source+="';\n";var variable=options.variable;if(!variable){source="with (obj) {\n"+source+"\n}\n"}source=(isEvaluating?source.replace(reEmptyStringLeading,""):source).replace(reEmptyStringMiddle,"$1").replace(reEmptyStringTrailing,"$1;");source="function("+(variable||"obj")+") {\n"+(variable?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(isEscaping?", __e = _.escape":"")+(isEvaluating?", __j = Array.prototype.join;\n"+"function print() { __p += __j.call(arguments, '') }\n":";\n")+source+"return __p\n}";var result=attempt(function(){return Function(importsKeys,sourceURL+"return "+source).apply(undefined,importsValues)});result.source=source;if(isError(result)){throw result}return result}function toLower(value){return toString(value).toLowerCase()}function toUpper(value){return toString(value).toUpperCase()}function trim(string,chars,guard){string=toString(string);if(string&&(guard||chars===undefined)){return string.replace(reTrim,"")}if(!string||!(chars=baseToString(chars))){return string}var strSymbols=stringToArray(string),chrSymbols=stringToArray(chars),start=charsStartIndex(strSymbols,chrSymbols),end=charsEndIndex(strSymbols,chrSymbols)+1;return castSlice(strSymbols,start,end).join("")}function trimEnd(string,chars,guard){string=toString(string);if(string&&(guard||chars===undefined)){return string.replace(reTrimEnd,"")}if(!string||!(chars=baseToString(chars))){return string}var strSymbols=stringToArray(string),end=charsEndIndex(strSymbols,stringToArray(chars))+1;return castSlice(strSymbols,0,end).join("")}function trimStart(string,chars,guard){string=toString(string);if(string&&(guard||chars===undefined)){return string.replace(reTrimStart,"")}if(!string||!(chars=baseToString(chars))){return string}var strSymbols=stringToArray(string),start=charsStartIndex(strSymbols,stringToArray(chars));return castSlice(strSymbols,start).join("")}function truncate(string,options){var length=DEFAULT_TRUNC_LENGTH,omission=DEFAULT_TRUNC_OMISSION;if(isObject(options)){var separator="separator"in options?options.separator:separator;length="length"in options?toInteger(options.length):length;omission="omission"in options?baseToString(options.omission):omission}string=toString(string);var strLength=string.length;if(hasUnicode(string)){var strSymbols=stringToArray(string);strLength=strSymbols.length}if(length>=strLength){return string}var end=length-stringSize(omission);if(end<1){return omission}var result=strSymbols?castSlice(strSymbols,0,end).join(""):string.slice(0,end);if(separator===undefined){return result+omission}if(strSymbols){end+=result.length-end}if(isRegExp(separator)){if(string.slice(end).search(separator)){var match,substring=result;if(!separator.global){separator=RegExp(separator.source,toString(reFlags.exec(separator))+"g")}separator.lastIndex=0;while(match=separator.exec(substring)){var newEnd=match.index}result=result.slice(0,newEnd===undefined?end:newEnd)}}else if(string.indexOf(baseToString(separator),end)!=end){var index=result.lastIndexOf(separator);if(index>-1){result=result.slice(0,index)}}return result+omission}function unescape(string){string=toString(string);return string&&reHasEscapedHtml.test(string)?string.replace(reEscapedHtml,unescapeHtmlChar):string}var upperCase=createCompounder(function(result,word,index){return result+(index?" ":"")+word.toUpperCase()});var upperFirst=createCaseFirst("toUpperCase");function words(string,pattern,guard){string=toString(string);pattern=guard?undefined:pattern;if(pattern===undefined){return hasUnicodeWord(string)?unicodeWords(string):asciiWords(string)}return string.match(pattern)||[]}var attempt=baseRest(function(func,args){try{return apply(func,undefined,args)}catch(e){return isError(e)?e:new Error(e)}});var bindAll=flatRest(function(object,methodNames){arrayEach(methodNames,function(key){key=toKey(key);baseAssignValue(object,key,bind(object[key],object))});return object});function cond(pairs){var length=pairs==null?0:pairs.length,toIteratee=getIteratee();pairs=!length?[]:arrayMap(pairs,function(pair){if(typeof pair[1]!="function"){throw new TypeError(FUNC_ERROR_TEXT)}return[toIteratee(pair[0]),pair[1]]});return baseRest(function(args){var index=-1;while(++indexMAX_SAFE_INTEGER){return[]}var index=MAX_ARRAY_LENGTH,length=nativeMin(n,MAX_ARRAY_LENGTH);iteratee=getIteratee(iteratee);n-=MAX_ARRAY_LENGTH;var result=baseTimes(length,iteratee);while(++index0||end<0)){return new LazyWrapper(result)}if(start<0){result=result.takeRight(-start)}else if(start){result=result.drop(start)}if(end!==undefined){end=toInteger(end);result=end<0?result.dropRight(-end):result.take(end-start)}return result};LazyWrapper.prototype.takeRightWhile=function(predicate){return this.reverse().takeWhile(predicate).reverse()};LazyWrapper.prototype.toArray=function(){return this.take(MAX_ARRAY_LENGTH)};baseForOwn(LazyWrapper.prototype,function(func,methodName){var checkIteratee=/^(?:filter|find|map|reject)|While$/.test(methodName),isTaker=/^(?:head|last)$/.test(methodName),lodashFunc=lodash[isTaker?"take"+(methodName=="last"?"Right":""):methodName],retUnwrapped=isTaker||/^find/.test(methodName);if(!lodashFunc){return}lodash.prototype[methodName]=function(){var value=this.__wrapped__,args=isTaker?[1]:arguments,isLazy=value instanceof LazyWrapper,iteratee=args[0],useLazy=isLazy||isArray(value);var interceptor=function(value){var result=lodashFunc.apply(lodash,arrayPush([value],args));return isTaker&&chainAll?result[0]:result};if(useLazy&&checkIteratee&&typeof iteratee=="function"&&iteratee.length!=1){isLazy=useLazy=false}var chainAll=this.__chain__,isHybrid=!!this.__actions__.length,isUnwrapped=retUnwrapped&&!chainAll,onlyLazy=isLazy&&!isHybrid;if(!retUnwrapped&&useLazy){value=onlyLazy?value:new LazyWrapper(this);var result=func.apply(value,args);result.__actions__.push({func:thru,args:[interceptor],thisArg:undefined});return new LodashWrapper(result,chainAll)}if(isUnwrapped&&onlyLazy){return func.apply(this,args)}result=this.thru(interceptor);return isUnwrapped?isTaker?result.value()[0]:result.value():result}});arrayEach(["pop","push","shift","sort","splice","unshift"],function(methodName){var func=arrayProto[methodName],chainName=/^(?:push|sort|unshift)$/.test(methodName)?"tap":"thru",retUnwrapped=/^(?:pop|shift)$/.test(methodName);lodash.prototype[methodName]=function(){var args=arguments;if(retUnwrapped&&!this.__chain__){var value=this.value();return func.apply(isArray(value)?value:[],args)}return this[chainName](function(value){return func.apply(isArray(value)?value:[],args)})}});baseForOwn(LazyWrapper.prototype,function(func,methodName){var lodashFunc=lodash[methodName];if(lodashFunc){var key=lodashFunc.name+"",names=realNames[key]||(realNames[key]=[]);names.push({name:methodName,func:lodashFunc})}});realNames[createHybrid(undefined,WRAP_BIND_KEY_FLAG).name]=[{name:"wrapper",func:undefined}];LazyWrapper.prototype.clone=lazyClone;LazyWrapper.prototype.reverse=lazyReverse;LazyWrapper.prototype.value=lazyValue;lodash.prototype.at=wrapperAt;lodash.prototype.chain=wrapperChain;lodash.prototype.commit=wrapperCommit;lodash.prototype.next=wrapperNext;lodash.prototype.plant=wrapperPlant;lodash.prototype.reverse=wrapperReverse;lodash.prototype.toJSON=lodash.prototype.valueOf=lodash.prototype.value=wrapperValue;lodash.prototype.first=lodash.prototype.head;if(symIterator){lodash.prototype[symIterator]=wrapperToIterator}return lodash};var _=runInContext();if(true){root._=_;!(__WEBPACK_AMD_DEFINE_RESULT__=function(){return _}.call(exports,__webpack_require__,exports,module),__WEBPACK_AMD_DEFINE_RESULT__!==undefined&&(module.exports=__WEBPACK_AMD_DEFINE_RESULT__))}else if(freeModule){(freeModule.exports=_)._=_;freeExports._=_}else{root._=_}}).call(this)}).call(exports,function(){return this}(),__webpack_require__(4)(module))},function(module,exports){module.exports=function(module){if(!module.webpackPolyfill){module.deprecate=function(){};module.paths=[];module.children=[];module.webpackPolyfill=1}return module}},function(module,exports,__webpack_require__){var extend=__webpack_require__(6);var eventOptions={UIEvent:function(){return{view:document.defaultView}},FocusEvent:function(){return eventOptions.UIEvent.apply(this,arguments)},MouseEvent:function(type){return{button:0,bubbles:type!=="mouseenter"&&type!=="mouseleave",cancelable:type!=="mouseenter"&&type!=="mouseleave",ctrlKey:false,altKey:false,shiftKey:false,metaKey:false,clientX:1,clientY:1,screenX:0,screenY:0,view:document.defaultView,relatedTarget:document.documentElement}},WheelEvent:function(type){return eventOptions.MouseEvent.apply(this,arguments)},KeyboardEvent:function(){return{view:document.defaultView,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false,keyCode:0}}};var eventTypes={beforeprint:"Event",afterprint:"Event",beforeunload:"Event",abort:"Event",error:"Event",change:"Event",submit:"Event",reset:"Event",cached:"Event",canplay:"Event",canplaythrough:"Event",chargingchange:"Event",chargingtimechange:"Event",checking:"Event",close:"Event",downloading:"Event",durationchange:"Event",emptied:"Event",ended:"Event",fullscreenchange:"Event",fullscreenerror:"Event",invalid:"Event",levelchange:"Event",loadeddata:"Event",loadedmetadata:"Event",noupdate:"Event",obsolete:"Event",offline:"Event",online:"Event",open:"Event",orientationchange:"Event",pause:"Event",pointerlockchange:"Event",pointerlockerror:"Event",copy:"Event",cut:"Event",paste:"Event",play:"Event",playing:"Event",ratechange:"Event",readystatechange:"Event",seeked:"Event",seeking:"Event",stalled:"Event",success:"Event",suspend:"Event",timeupdate:"Event",updateready:"Event",visibilitychange:"Event",volumechange:"Event",waiting:"Event",load:"UIEvent",unload:"UIEvent",resize:"UIEvent",scroll:"UIEvent",select:"UIEvent",drag:"MouseEvent",dragenter:"MouseEvent",dragleave:"MouseEvent",dragover:"MouseEvent",dragstart:"MouseEvent",dragend:"MouseEvent",drop:"MouseEvent",touchcancel:"UIEvent",touchend:"UIEvent",touchenter:"UIEvent",touchleave:"UIEvent",touchmove:"UIEvent",touchstart:"UIEvent",blur:"UIEvent",focus:"UIEvent",focusin:"UIEvent",focusout:"UIEvent",input:"UIEvent",show:"MouseEvent",click:"MouseEvent",dblclick:"MouseEvent",mouseenter:"MouseEvent",mouseleave:"MouseEvent",mousedown:"MouseEvent",mouseup:"MouseEvent",mouseover:"MouseEvent",mousemove:"MouseEvent",mouseout:"MouseEvent",contextmenu:"MouseEvent",wheel:"WheelEvent",message:"MessageEvent",storage:"StorageEvent",timeout:"StorageEvent",keydown:"KeyboardEvent",keypress:"KeyboardEvent",keyup:"KeyboardEvent",progress:"ProgressEvent",loadend:"ProgressEvent",loadstart:"ProgressEvent",popstate:"PopStateEvent",hashchange:"HashChangeEvent",transitionend:"TransitionEvent",compositionend:"CompositionEvent",compositionstart:"CompositionEvent",compositionupdate:"CompositionEvent",pagehide:"PageTransitionEvent",pageshow:"PageTransitionEvent"};var eventInit={Event:"initEvent",UIEvent:"initUIEvent",FocusEvent:"initUIEvent",MouseEvent:"initMouseEvent",WheelEvent:"initMouseEvent",MessageEvent:"initMessageEvent",StorageEvent:"initStorageEvent",KeyboardEvent:"initKeyboardEvent",ProgressEvent:"initEvent",PopStateEvent:"initEvent",TransitionEvent:"initEvent",HashChangeEvent:"initHashChangeEvent",CompositionEvent:"initCompositionEvent",DeviceMotionEvent:"initDeviceMotionEvent",PageTransitionEvent:"initEvent",DeviceOrientationEvent:"initDeviceOrientationEvent"};var eventParameters={initEvent:[],initUIEvent:["view","detail"],initKeyboardEvent:["view","char","key","location","modifiersList","repeat","locale"],initKeyEvent:["view","ctrlKey","altKey","shiftKey","metaKey","keyCode","charCode"],initMouseEvent:["view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget"],initHashChangeEvent:["oldURL","newURL"],initCompositionEvent:["view","data","locale"],initDeviceMotionEvent:["acceleration","accelerationIncludingGravity","rotationRate","interval"],initDeviceOrientationEvent:["alpha","beta","gamma","absolute"],initMessageEvent:["data","origin","lastEventId","source"],initStorageEvent:["key","oldValue","newValue","url","storageArea"]};var eventConstructors={UIEvent:window.UIEvent,FocusEvent:window.FocusEvent,MouseEvent:window.MouseEvent,WheelEvent:window.MouseEvent,KeyboardEvent:window.KeyboardEvent};function getOverrides(eventType,options){if(eventType==="KeyboardEvent"&&options){return{keyCode:options.keyCode||0,key:options.key||0,which:options.which||options.keyCode||0}}}exports.generate=function(type,options){if(!eventTypes.hasOwnProperty(type)){throw new SyntaxError("Unsupported event type")}var eventType=eventTypes[type];var event;var key;var overrides=getOverrides(eventType,options);if(!(options instanceof window.Event)){if(eventType in eventOptions){options=extend({bubbles:true,cancelable:true},eventOptions[eventType](type,options),options)}else{options=extend({bubbles:true,cancelable:true},options)}}var Constructor=eventConstructors[eventType]||window.Event;try{event=new Constructor(type,options);for(key in overrides){Object.defineProperty(event,key,{value:overrides[key]})}return event}catch(e){}var ua=window.navigator.userAgent.toLowerCase();var msie=Math.max(ua.indexOf("msie"),ua.indexOf("trident"));if(msie>=0&&eventType==="KeyboardEvent"){eventType="UIEvent"}var initEvent=eventInit[eventType];if(!document.createEvent){event=extend(document.createEventObject(),options);for(key in overrides){Object.defineProperty(event,key,{value:overrides[key]})}return event}event=extend(document.createEvent(eventType),options);if(initEvent==="initKeyboardEvent"){if(event[initEvent]===void 0){initEvent="initKeyEvent"}else if(!("modifiersList"in options)){var mods=[];if(options.metaKey)mods.push("Meta");if(options.altKey)mods.push("Alt");if(options.shiftKey)mods.push("Shift");if(options.ctrlKey)mods.push("Control");options["modifiersList"]=mods.join(" ")}}var args=eventParameters[initEvent].map(function(parameter){return options[parameter]});event[initEvent].apply(event,[type,options.bubbles,options.cancelable].concat(args));for(key in overrides){Object.defineProperty(event,key,{value:overrides[key]})}return event};exports.simulate=function(element,type,options){var event=exports.generate(type,options);if(!document.createEvent){return element.fireEvent("on"+type,event)}return element.dispatchEvent(event)}},function(module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(target){for(var i=1;i", 22 | "contributors": [ 23 | "Casey Webb ", 24 | "Michael Barshinger " 25 | ], 26 | "license": "WTFPL", 27 | "devDependencies": { 28 | "babel-core": "^6.7.6", 29 | "babel-loader": "^6.2.4", 30 | "babel-preset-es2015": "^6.6.0", 31 | "chai": "^3.5.0", 32 | "codeclimate-test-reporter": "^0.3.0", 33 | "coveralls": "^2.11.9", 34 | "eslint": "^3.0.1", 35 | "eslint-config-profiscience": "^1.0.1", 36 | "isparta-loader": "^2.0.0", 37 | "jquery": "^3.0.0", 38 | "karma": "^1.1.0", 39 | "karma-chrome-launcher": "^1.0.1", 40 | "karma-coverage": "^1.0.0", 41 | "karma-firefox-launcher": "^1.0.0", 42 | "karma-mocha": "^1.1.1", 43 | "karma-mocha-reporter": "^2.0.1", 44 | "karma-sourcemap-loader": "^0.3.7", 45 | "karma-webpack": "^2.0.1", 46 | "knockout": "^3.4.0", 47 | "lodash": "^4.11.2", 48 | "mocha": "^3.0.2", 49 | "mocha-loader": "^0.7.1", 50 | "pre-commit": "^1.1.2", 51 | "simulate-event": "^1.4.0", 52 | "sinon": "^2.0.0-pre", 53 | "uglify-js": "^2.4.24", 54 | "webpack": "^1.13.0" 55 | }, 56 | "peerDependencies": { 57 | "jquery": ">= 2.2.0", 58 | "knockout": "^3.4.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const $ = require('jquery') 5 | const _ = require('lodash') 6 | const simulateEvent = require('simulate-event') 7 | 8 | $.fn.simulate = function(eventName, value) { 9 | if (value) { 10 | this.val(value) 11 | } 12 | if (value) this.val(value) 13 | 14 | var target = this.get(0) 15 | simulateEvent.simulate(target, eventName, value) 16 | ko.tasks.runEarly() 17 | } 18 | 19 | $.fn.waitForBinding = function(bindingName) { 20 | if (!ko.bindingHandlers[bindingName]) 21 | throw new Error(`binding does not exist: ${bindingName}`) 22 | 23 | const binding = ko.bindingHandlers[bindingName].init 24 | ? ko.bindingHandlers[bindingName].init.bind(ko.bindingHandlers[bindingName].init) 25 | : function() {} 26 | 27 | return new Promise((resolve) => { 28 | const $el = this 29 | ko.bindingHandlers[bindingName].init = (el) => { 30 | if ($el.get(0) === el) { 31 | binding(...arguments) 32 | ko.tasks.schedule(() => { 33 | ko.bindingHandlers[bindingName].init = binding 34 | resolve($el) 35 | }) 36 | } else { 37 | binding(...arguments) 38 | } 39 | } 40 | }) 41 | } 42 | 43 | $.fn.waitForProperty = function(key, val, timeout = 2000) { 44 | const prop = access(key.split('.'), this.$data()) 45 | return new Promise((resolve, reject) => { 46 | if (matches(prop())) { 47 | return resolve(prop()) 48 | } 49 | 50 | const timeoutId = setTimeout(() => { 51 | killMe.dispose() 52 | reject(`Timed out waiting for property ${key}`) 53 | }, timeout) 54 | 55 | const killMe = prop.subscribe((v) => { 56 | if (!matches(v)) { 57 | return 58 | } 59 | 60 | clearTimeout(timeoutId) 61 | killMe.dispose() 62 | ko.tasks.runEarly() 63 | resolve(v) 64 | }) 65 | }) 66 | 67 | function access([k, ...ks], obj) { 68 | const p = obj[k] 69 | return ks.length > 0 70 | ? access(ks, p) 71 | : p 72 | } 73 | 74 | function matches(v) { 75 | return typeof v !== 'undefined' && (typeof val === 'undefined' || (val instanceof RegExp 76 | ? val.test(v) 77 | : v === val)) 78 | } 79 | } 80 | 81 | $.fn.$data = function() { 82 | return this.children().length > 0 83 | ? ko.dataFor(this.children().get(0)) 84 | : ko.dataFor(ko.virtualElements.firstChild(this.get(0))) 85 | } 86 | 87 | $.fn.$context = function() { 88 | return this.children().length > 0 89 | ? ko.contextFor(this.children().get(0)) 90 | : ko.contextFor(ko.virtualElements.firstChild(this.get(0))) 91 | } 92 | 93 | ko.components.loaders.unshift({ 94 | loadComponent(name, component, done) { 95 | if (!component.viewModel) { 96 | class ViewModel { constructor(params) { ko.utils.extend(this, params) } } 97 | component.viewModel = ViewModel 98 | } 99 | done(null) 100 | }, 101 | loadViewModel(name, config, done) { 102 | if (typeof config === 'function') { 103 | done((params) => { 104 | const viewModel = new config(params) 105 | viewModel._calledWith = params 106 | return viewModel 107 | }, done) 108 | } else if (config.createViewModel) { 109 | done((params, componentInfo) => { 110 | const viewModel = config.createViewModel(params, componentInfo) 111 | viewModel._calledWith = params 112 | return viewModel 113 | }, done) 114 | } else { 115 | done(null) 116 | } 117 | } 118 | }) 119 | 120 | $.fn.getComponentParams = function() { 121 | return ko.contextFor(ko.virtualElements.firstChild(this.get(0))).$component._calledWith 122 | } 123 | 124 | function renderComponent(component, _params = {}, _bindingCtx = {}) { 125 | const _component = ko.observable('_SUT') 126 | const $el = $('
') 127 | component.synchronous = true 128 | 129 | if (ko.components.isRegistered('_SUT')) { 130 | ko.components.unregister('_SUT') 131 | } 132 | 133 | ko.components.register('_SUT', component) 134 | ko.bindingHandlers._setContext = { 135 | init(el, valueAccessor, allBindings, viewModel, bindingContext) { 136 | _.merge(bindingContext, _bindingCtx) 137 | } 138 | } 139 | 140 | $('body').html($el) 141 | ko.applyBindings(_.merge({ _component, _params }), $el.get(0)) 142 | ko.tasks.runEarly() 143 | 144 | ko.components.unregister('_SUT') 145 | ko.bindingHandlers._setContext = (void 0) 146 | 147 | $el.dispose = function() { 148 | ko.components.register('_NULL', { template: '' }) 149 | _component('_NULL') 150 | ko.tasks.runEarly() 151 | ko.components.unregister('_NULL') 152 | $el.remove() 153 | } 154 | 155 | return $el 156 | } 157 | 158 | function renderHtml({ template, viewModel = {} }) { 159 | let $el 160 | try { $el = $(template) } 161 | catch (e) { $el = $('').text(template) } 162 | 163 | $('body').html($el) 164 | 165 | if (typeof viewModel === 'function') { 166 | ko.applyBindings(new viewModel(), $el.get(0)) 167 | } else { 168 | ko.applyBindings(viewModel, $el.get(0)) 169 | } 170 | 171 | ko.tasks.runEarly() 172 | 173 | return $el 174 | } 175 | 176 | module.exports = { renderComponent, renderHtml } 177 | -------------------------------------------------------------------------------- /test/$context.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const { renderComponent } = require('../src') 5 | const { expect } = require('chai') 6 | 7 | describe('$context' , () => { 8 | let $el 9 | 10 | afterEach(() => { 11 | $el.dispose() 12 | }) 13 | 14 | it('should be able to access the bindingContext', () => { 15 | const foo = 'foo' 16 | ko.bindingHandlers.checkContext = { 17 | init(el, valueAccessor, allBindings, viewModel, bindingContext) { 18 | expect(bindingContext.greeting()).to.equal(foo) 19 | } 20 | } 21 | 22 | $el = renderComponent({ 23 | template: '' 24 | }, 25 | {}, 26 | { 27 | greeting: ko.observable(foo) 28 | }) 29 | 30 | expect($el.html()).to.contain(foo) 31 | expect($el.$context().greeting()).to.equal(foo) 32 | }) 33 | 34 | it('should be able to update the bindingContext', () => { 35 | let v = 'foo' 36 | ko.bindingHandlers.checkContext = { 37 | update(el, valueAccessor, allBindings, viewModel, bindingContext) { 38 | expect(bindingContext.greeting()).to.equal(v) 39 | } 40 | } 41 | 42 | $el = renderComponent({ 43 | template: '' 44 | }, 45 | {}, 46 | { 47 | greeting: ko.observable(v) 48 | }) 49 | 50 | v = 'bar' 51 | 52 | $el.$context().greeting(v) 53 | ko.tasks.runEarly() 54 | expect($el.html()).to.contain(v) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/$data.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const { renderComponent } = require('../src') 5 | const { expect } = require('chai') 6 | 7 | describe('$.fn.$data' , () => { 8 | let $el 9 | 10 | afterEach(() => { 11 | $el.dispose() 12 | }) 13 | 14 | it('should be able to access viewmodel', () => { 15 | const x = 'x' 16 | 17 | $el = renderComponent({ 18 | template: '', 19 | viewModel() { this.greeting = ko.observable(x) } 20 | }) 21 | 22 | expect($el.html()).to.contain(x) 23 | expect($el.$data().greeting()).to.equal(x) 24 | }) 25 | 26 | it('should be able to update viewmodel', () => { 27 | const x = 'x' 28 | 29 | $el = renderComponent({ 30 | template: '', 31 | viewModel() { this.greeting = ko.observable() } 32 | }) 33 | 34 | $el.$data().greeting(x) 35 | expect($el.$data().greeting()).to.equal(x) 36 | expect($el.html()).to.contain(x) 37 | }) 38 | 39 | }) 40 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/getComponentParams.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const { renderHtml } = require('../src') 5 | const { expect } = require('chai') 6 | 7 | describe('getComponentParams' , () => { 8 | afterEach(() => { 9 | ko.components.unregister('foo') 10 | }) 11 | 12 | it('can access/update component params with a function viewModel', () => { 13 | ko.components.register('foo', { 14 | template: '
', 15 | viewModel(params) { this.baz = params.baz } 16 | }) 17 | 18 | const $el = renderHtml({ 19 | template: ` 20 |
21 | 22 |
23 | `, 24 | viewModel: { 25 | bar: 'bar', 26 | baz: ko.observable('baz') 27 | } 28 | }) 29 | 30 | expect($el.find('foo')).to.exist 31 | expect($el.find('foo').getComponentParams().bar).to.equal('bar') 32 | expect($el.find('foo').getComponentParams().baz()).to.equal('baz') 33 | 34 | $el.find('foo').getComponentParams().baz('qux') 35 | expect($el.find('foo').getComponentParams().baz()).to.equal('qux') 36 | expect($el.find('foo').html()).contains('qux') 37 | 38 | ko.components.unregister('foo') 39 | }) 40 | 41 | it('can access/update component params with a class viewModel', () => { 42 | ko.components.register('foo', { 43 | template: '
', 44 | viewModel: class ViewModel { constructor(params) { this.baz = params.baz } } 45 | }) 46 | 47 | const $el = renderHtml({ 48 | template: ` 49 |
50 | 51 |
52 | `, 53 | viewModel: { 54 | bar: 'bar', 55 | baz: ko.observable('baz') 56 | } 57 | }) 58 | 59 | expect($el.find('foo')).to.exist 60 | expect($el.find('foo').getComponentParams().bar).to.equal('bar') 61 | expect($el.find('foo').getComponentParams().baz()).to.equal('baz') 62 | 63 | $el.find('foo').getComponentParams().baz('qux') 64 | expect($el.find('foo').getComponentParams().baz()).to.equal('qux') 65 | expect($el.find('foo').html()).contains('qux') 66 | 67 | ko.components.unregister('foo') 68 | }) 69 | 70 | it('can access/update component params with a factory viewModel', () => { 71 | ko.components.register('foo', { 72 | template: '
', 73 | viewModel: { 74 | createViewModel(params) { 75 | class ViewModel { constructor(p) { this.baz = p.baz } } 76 | return new ViewModel(params) 77 | } 78 | } 79 | }) 80 | 81 | const $el = renderHtml({ 82 | template: ` 83 |
84 | 85 |
86 | `, 87 | viewModel: { 88 | bar: 'bar', 89 | baz: ko.observable('baz') 90 | } 91 | }) 92 | 93 | expect($el.find('foo')).to.exist 94 | expect($el.find('foo').getComponentParams().bar).to.equal('bar') 95 | expect($el.find('foo').getComponentParams().baz()).to.equal('baz') 96 | 97 | $el.find('foo').getComponentParams().baz('qux') 98 | expect($el.find('foo').getComponentParams().baz()).to.equal('qux') 99 | expect($el.find('foo').html()).contains('qux') 100 | 101 | ko.components.unregister('foo') 102 | }) 103 | 104 | it('can access/update component params with no viewModel', () => { 105 | ko.components.register('foo', { 106 | template: '
' 107 | }) 108 | 109 | const $el = renderHtml({ 110 | template: ` 111 |
112 | 113 |
114 | `, 115 | viewModel: { 116 | bar: 'bar', 117 | baz: ko.observable('baz') 118 | } 119 | }) 120 | 121 | expect($el.find('foo')).to.exist 122 | expect($el.find('foo').getComponentParams().bar).to.equal('bar') 123 | expect($el.find('foo').getComponentParams().baz()).to.equal('baz') 124 | 125 | $el.find('foo').getComponentParams().baz('qux') 126 | expect($el.find('foo').getComponentParams().baz()).to.equal('qux') 127 | expect($el.find('foo').html()).contains('qux') 128 | 129 | ko.components.unregister('foo') 130 | }) 131 | 132 | it('can\'t access/update component params with a shared instance', () => { 133 | ko.components.register('foo', { 134 | template: '
', 135 | viewModel: { instance: {} } 136 | }) 137 | 138 | const $el = renderHtml({ 139 | template: ` 140 |
141 | 142 |
143 | `, 144 | viewModel: { 145 | bar: 'bar', 146 | baz: ko.observable('baz') 147 | } 148 | }) 149 | 150 | expect($el.find('foo')).to.exist 151 | expect($el.find('foo').getComponentParams()).to.be.undefined 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /test/renderComponent.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { renderComponent } = require('../src') 4 | const { expect } = require('chai') 5 | const sinon = require('sinon') 6 | 7 | describe('renderComponent' , () => { 8 | let $el 9 | 10 | afterEach(() => { 11 | $el.dispose() 12 | }) 13 | 14 | it('works with elements', () => { 15 | $el = renderComponent({ 16 | template: '', 17 | viewModel() { this.greeting = 'Hello Component' } 18 | }) 19 | 20 | expect($el).to.exist 21 | expect($el.html()).contains('Hello Component') 22 | }) 23 | 24 | it('works with text nodes', () => { 25 | $el = renderComponent({ 26 | template: ` 27 | Hello Component 28 | ` 29 | }) 30 | 31 | expect($el).to.exist 32 | expect($el.html()).contains('Hello Component') 33 | }) 34 | 35 | it('should clean up after itself in the case of failure', () => { 36 | try { 37 | renderComponent({ template: '
' }) 38 | } catch (e) { 39 | // do nothing 40 | } 41 | 42 | $el = renderComponent({ 43 | template: ` 44 | Hello Component 45 | ` 46 | }) 47 | 48 | expect($el).to.exist 49 | expect($el.html()).contains('Hello Component') 50 | }) 51 | }) 52 | 53 | describe('renderComponent', () => { 54 | describe('#dispose', () => { 55 | const dispose = sinon.spy() 56 | 57 | it('calls the dispose callback', () => { 58 | const $el = renderComponent({ 59 | template: '', 60 | viewModel() { this.dispose = dispose } 61 | }) 62 | 63 | $el.dispose() 64 | expect(dispose.called).to.be.true 65 | }) 66 | 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/renderHtml.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { renderHtml } = require('../src') 4 | const { expect } = require('chai') 5 | 6 | describe('renderHtml' , () => { 7 | it('works with elements', () => { 8 | const $el = renderHtml({ template: '
Hello World!
' }) 9 | expect($el).to.exist 10 | expect($el.html()).equals('Hello World!') 11 | }) 12 | 13 | it('works with text nodes', () => { 14 | const $el = renderHtml({ template: 'Hello World!' }) 15 | expect($el).to.exist 16 | expect($el.html()).contains('Hello World!') 17 | }) 18 | 19 | it('works with viewModel object', () => { 20 | const $el = renderHtml({ 21 | template: '
', 22 | viewModel: { greeting: 'Hello Text Binding' } 23 | }) 24 | expect($el).to.exist 25 | expect($el.html()).equals('Hello Text Binding') 26 | }) 27 | 28 | it('works with viewModel function', () => { 29 | const $el = renderHtml({ 30 | template: '
', 31 | viewModel() { 32 | this.greeting = 'Hello Text Binding' 33 | } 34 | }) 35 | expect($el).to.exist 36 | expect($el.html()).equals('Hello Text Binding') 37 | }) 38 | 39 | it('works with viewModel es6 class', () => { 40 | const $el = renderHtml({ 41 | template: '
', 42 | viewModel: class ViewModel { 43 | constructor() { 44 | this.greeting = 'Hello Text Binding' 45 | } 46 | } 47 | }) 48 | expect($el).to.exist 49 | expect($el.html()).equals('Hello Text Binding') 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/sample.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const { expect } = require('chai') 5 | const sinon = require('sinon') 6 | const { renderComponent } = require('../src') 7 | 8 | class LoginComponent { 9 | constructor() { 10 | this.username = ko.observable() 11 | this.password = ko.observable() 12 | } 13 | submit() {} 14 | } 15 | 16 | describe('sample login component' , () => { 17 | let $el 18 | 19 | before(() => { 20 | $el = renderComponent({ 21 | viewModel: LoginComponent, 22 | template: ` 23 |
24 | 25 | 26 | 27 |
` 28 | }) 29 | }) 30 | 31 | after(() => { 32 | $el.dispose() 33 | }) 34 | 35 | it('renders correctly', () => { 36 | expect($el).to.exist 37 | expect($el.find('form'), 'contains a form').to.exist 38 | expect($el.find('input[name="user"]', 'contains a username field')).to.exist 39 | expect($el.find('input[name="pass"]', 'contains a password field')).to.exist 40 | expect($el.find('input[type="submit"]', 'contains a submit button')).to.exist 41 | }) 42 | 43 | it('updates the viewmodel when a value is changed', () => { 44 | $el.find('input[name=user]').simulate('change', 'john') 45 | expect($el.$data().username()).equals('john') 46 | }) 47 | 48 | it('can submit the form', () => { 49 | const submitSpy = sinon.spy($el.$data().submit) 50 | $el.find('input[name=user]').simulate('change', 'john') 51 | $el.find('input[name=pass]').simulate('change', 'p455w0rd') 52 | $el.find('input[type="submit"]').simulate('click') 53 | 54 | expect(submitSpy).to.be.called 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/waitForBinding.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const { renderComponent } = require('../src') 5 | const { expect } = require('chai') 6 | 7 | describe('waitForBinding' , () => { 8 | let $el 9 | 10 | before(() => { 11 | ko.bindingHandlers.asyncText = { 12 | init(el, valueAccessor) { 13 | setTimeout(() => { 14 | ko.applyBindingsToNode(el, { 15 | text: valueAccessor() 16 | }) 17 | }, 200) 18 | return { controlsDescendantBindings: true } 19 | } 20 | } 21 | 22 | ko.bindingHandlers.asyncVisible = { 23 | init(el, valueAccessor) { 24 | setTimeout(() => { 25 | ko.applyBindingsToNode(el, { 26 | visible: valueAccessor() 27 | }) 28 | }, 200) 29 | return { controlsDescendantBindings: true } 30 | } 31 | } 32 | }) 33 | 34 | after(() => { 35 | ko.options.deferUpdates = false 36 | ko.bindingHandlers.asyncText = (void 0) 37 | }) 38 | 39 | afterEach(() => { 40 | $el.dispose() 41 | }) 42 | 43 | it('should throw error if binding is not defined', (done) => { 44 | $el = renderComponent({ 45 | template: '', 46 | viewModel() { } 47 | }) 48 | 49 | const $$el = $el.find('.test-me') 50 | try { 51 | $$el.waitForBinding('notdefined') 52 | } 53 | catch (e) { 54 | expect(e.message).to.contain('binding does not exist') 55 | } 56 | done() 57 | }) 58 | 59 | it('works with bindings that have init funcs', (done) => { 60 | $el = renderComponent({ 61 | template: ` 62 | 63 | 64 | `, 65 | viewModel() { this.greeting = 'Hello Component' } 66 | }) 67 | 68 | const $$el = $el.find('.test-me') 69 | $$el.waitForBinding('text').then(() => { 70 | expect($$el.html()).to.contain('Hello Component') 71 | done() 72 | }) 73 | }) 74 | 75 | it('works with bindings that DON\'T have init funcs', (done) => { 76 | $el = renderComponent({ 77 | template: ` 78 | 79 | `, 80 | viewModel() { this.visible = false } 81 | }) 82 | 83 | const $$el = $el.find('.test-me') 84 | $$el.waitForBinding('visible').then(() => { 85 | expect($$el).to.not.be.visible 86 | done() 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /test/waitForProperty.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ko = require('knockout') 4 | const sinon = require('sinon') 5 | const { renderComponent } = require('../src') 6 | const { expect } = require('chai') 7 | 8 | describe('waitForProperty' , function() { // eslint-disable-line 9 | let $el 10 | 11 | afterEach(() => { 12 | $el.dispose() 13 | }) 14 | 15 | it('waits for property to be defined when no value specified', (done) => { 16 | $el = renderComponent({ 17 | template: '', 18 | viewModel() { 19 | this.greeting = ko.observable() 20 | setTimeout(() => this.greeting('Hello, World!'), 200) 21 | } 22 | }) 23 | 24 | $el.waitForProperty('greeting').then((v) => { 25 | expect(v).to.equal('Hello, World!') 26 | expect($el.$data().greeting()).to.equal('Hello, World!') 27 | done() 28 | }) 29 | }) 30 | 31 | it('works with deep properties (ex. `foo.bar.baz` to access { foo: { bar: { baz: null } } })', (done) => { 32 | $el = renderComponent({ 33 | template: '', 34 | viewModel() { 35 | this.foo = { 36 | bar: ko.observable() 37 | } 38 | setTimeout(() => this.foo.bar('bar'), 200) 39 | } 40 | }) 41 | 42 | $el.waitForProperty('foo.bar').then((v) => { 43 | expect(v).to.equal('bar') 44 | expect($el.$data().foo.bar()).to.equal('bar') 45 | done() 46 | }) 47 | }) 48 | 49 | it('waits for property to be equal value specified', (done) => { 50 | $el = renderComponent({ 51 | template: '', 52 | viewModel() { 53 | this.greeting = ko.observable('Hello, World"') 54 | setTimeout(() => this.greeting('Good afternoon, World!'), 100) 55 | setTimeout(() => this.greeting('Goodbye, World!'), 200) 56 | } 57 | }) 58 | 59 | $el.waitForProperty('greeting', 'Goodbye, World!').then((v) => { 60 | expect(v).to.equal('Goodbye, World!') 61 | expect($el.$data().greeting()).to.equal('Goodbye, World!') 62 | done() 63 | }) 64 | }) 65 | 66 | it('waits for property to match regex specified', (done) => { 67 | $el = renderComponent({ 68 | template: '', 69 | viewModel() { 70 | this.greeting = ko.observable('Hello, World"') 71 | setTimeout(() => this.greeting('Good afternoon, World!'), 100) 72 | setTimeout(() => this.greeting('Goodbye, World!'), 200) 73 | } 74 | }) 75 | 76 | $el.waitForProperty('greeting', /bye/i).then((v) => { 77 | expect(v).to.equal('Goodbye, World!') 78 | expect($el.$data().greeting()).to.equal('Goodbye, World!') 79 | done() 80 | }) 81 | }) 82 | 83 | it('resolves immediately if not undefined and no value specified', (done) => { 84 | $el = renderComponent({ 85 | template: '', 86 | viewModel() { 87 | this.greeting = ko.observable('Hello, World!') 88 | } 89 | }) 90 | 91 | $el.waitForProperty('greeting').then((v) => { 92 | expect(v).to.equal('Hello, World!') 93 | expect($el.$data().greeting()).to.equal('Hello, World!') 94 | done() 95 | }) 96 | }) 97 | 98 | it('resolves immediately if already equal to value specified', (done) => { 99 | $el = renderComponent({ 100 | template: '', 101 | viewModel() { 102 | this.greeting = ko.observable('Hello, World!') 103 | } 104 | }) 105 | 106 | $el.waitForProperty('greeting', 'Hello, World!').then((v) => { 107 | expect(v).to.equal('Hello, World!') 108 | expect($el.$data().greeting()).to.equal('Hello, World!') 109 | done() 110 | }) 111 | }) 112 | 113 | it('resolves immediately if already matches regex specified', (done) => { 114 | $el = renderComponent({ 115 | template: '', 116 | viewModel() { 117 | this.greeting = ko.observable('Hello, World!') 118 | } 119 | }) 120 | 121 | $el.waitForProperty('greeting', 'Hello, World!').then((v) => { 122 | expect(v).to.equal('Hello, World!') 123 | expect($el.$data().greeting()).to.equal('Hello, World!') 124 | done() 125 | }) 126 | }) 127 | 128 | it('times out after 2000ms by default', (done) => { 129 | const clock = sinon.useFakeTimers() 130 | 131 | $el = renderComponent({ 132 | template: '', 133 | viewModel() { 134 | this.greeting = ko.observable() 135 | } 136 | }) 137 | 138 | $el.waitForProperty('greeting').catch((err) => { 139 | expect(err).to.contain('Timed out') 140 | clock.restore() 141 | done() 142 | }) 143 | 144 | clock.tick(3000) 145 | }) 146 | 147 | it('can set a custom timeout', (done) => { 148 | const clock = sinon.useFakeTimers() 149 | 150 | $el = renderComponent({ 151 | template: '', 152 | viewModel() { 153 | this.greeting = ko.observable() 154 | } 155 | }) 156 | 157 | $el.waitForProperty('greeting', undefined, 500).catch((err) => { 158 | expect(err).to.contain('Timed out') 159 | clock.restore() 160 | done() 161 | }) 162 | 163 | clock.tick(1000) 164 | }) 165 | }) 166 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | 6 | output: { 7 | path: 'dist', 8 | filename: 'ko-component-tester.js', 9 | library: 'ko-component-tester', 10 | libraryTarget: 'umd' 11 | }, 12 | 13 | module: { 14 | loaders: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /(node_modules)/, 18 | loader: 'babel', 19 | query: { 20 | cacheDirectory: true, 21 | presets: ['es2015'] 22 | } 23 | } 24 | ] 25 | }, 26 | 27 | externals: { 28 | 'jquery': { 29 | root: 'jQuery', 30 | commonjs: 'jquery', 31 | commonjs2: 'jquery', 32 | amd: 'jquery' 33 | }, 34 | 'knockout': { 35 | root: 'ko', 36 | commonjs: 'knockout', 37 | commonjs2: 'knockout', 38 | amd: 'knockout' 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------