├── .gitignore ├── .project ├── LICENSE ├── README.md ├── browser ├── switchcase.js └── switchcase.min.js ├── examples ├── index.html └── recursion.html ├── index.js ├── package.json └── test ├── index.html ├── index.js └── issue3.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | switchcase 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Simon Y. Blackwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a5863bf7debd4dfbacb043f5b3996e3a)](https://www.codacy.com/app/syblackwell/switchcase?utm_source=github.com&utm_medium=referral&utm_content=anywhichway/switchcase&utm_campaign=Badge_Grade) 2 | 3 | # switchcase 4 | 5 | Declarative and functional switch supporting literals, functional tests, regular expressions, and object pattern matching. 6 | 7 | This module can be used to simplify code using `if then else` and `switch` statements. 8 | 9 | It can also be used as a [router](#router), e.g. 10 | 11 | ```javascript 12 | const router = switchcase() 13 | .route("/:id/:name",value => { 14 | console.log(logged = value); 15 | }); 16 | router.handle({req:{url:"https://www.somesite.com/1/joe"},res:{}}); 17 | ``` 18 | 19 | Or, it can be used as a powerful object selection pattern matcher, e.g. 20 | 21 | ```javascript 22 | switchcase([ 23 | {name:"joe",age:21,address:{city:"Seattle",zipcode:"98101"}}, 24 | {name:"mary",age:20,address:{city:"Seattle",zipcode:"90101"}}, 25 | {name:"joan",age:22,address:{city:"Bainbridge Island",zipcode:"98110"}]]) 26 | .match({age:(value) => value >=21,address:{city:"Seattle"}}); 27 | ``` 28 | 29 | # Installation 30 | 31 | npm install switchcase 32 | 33 | For browsers, use the files in the `browser` directory. 34 | 35 | # API 36 | 37 | Let's start with an example: 38 | 39 | ```javascript 40 | let sw = switchcase({ 41 | 1: "Case one, a literal", 42 | [(value) => value===2]: "Case two, a function", 43 | [/3/]: "Case three, a regular expression", 44 | [4]: () => "Case four, just demonstrating a functional value", 45 | default: "Defaulted" 46 | }); 47 | 48 | console.log(sw(1)); 49 | console.log(sw(2)); 50 | console.log(sw(3)); 51 | console.log(sw(4)()); // note the function invocation 52 | console.log(sw(5)); 53 | 54 | ``` 55 | 56 | ```javascript 57 | let sw = switchcase({},{call:true}) // if the switch values are functions, call them 58 | .case(1,() => console.log("Case one, a literal")) 59 | .case(value => value===2,() => console.log("Case two, a function")) 60 | .case(/3/, () => console.log("Case three, a regular expression")) 61 | .case(4, () => () => console.log("Case four, just demonstrating a functional value")) 62 | .default(() => console.log("Defaulted")); 63 | 64 | console.log(sw(1)); 65 | console.log(sw(2)); 66 | console.log(sw(3)); 67 | console.log(sw(4)()); // note the function invocation 68 | console.log(sw(5)); 69 | 70 | ``` 71 | 72 | ```javascript 73 | let sw = switchcase({ 74 | 1: () => console.log("Case one, a literal"), 75 | [(value) => value===2]: () => console.log("Case two, a function"), 76 | [/3/]: () => console.log("Case three, a regular expression"), 77 | [4]: () => () => console.log("Case four, just demonstrating a functional value"), 78 | default: () => console.log("Defaulted") 79 | },{call:true}); // if the switch values are functions, call them 80 | 81 | console.log(sw(1)); 82 | console.log(sw(2)); 83 | console.log(sw(3)); 84 | console.log(sw(4)()); // note the function invocation 85 | console.log(sw(5)); 86 | 87 | ``` 88 | 89 | 90 | will all print 91 | 92 | ``` 93 | Case one, a literal 94 | 95 | Case two, a function 96 | 97 | Case three, a regular expression 98 | 99 | Case four, just demonstrating a functional value 100 | 101 | Defaulted 102 | ``` 103 | 104 | In short, calling `switchcase` with an object will return a functional switch. By using [ES2015 object initializer syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer), the properties of the object can look like they are functions or regular expressions. 105 | 106 | The returned function can also be enhanced through the use of chained calls: 107 | 108 | 1) `case(test,value)`, which adds a test and value. 109 | 110 | 2) `route(test,handler)`, which turns the switch into a router adds a test and handler. 111 | 112 | 3) `default(value)`, which sets the default result. 113 | 114 | 4) `otherwise(value)`, an alias for `default`. 115 | 116 | 117 | You can also optionaly use `sw.match(value)` or `sw.handle(value)` in place of direct invocation, `sw(value)`, if you think it assists with clarity. 118 | 119 | ## Optional Argument 120 | 121 | `switchcase` can take a second argument `switchcase(,)`. 122 | 123 | If the second argument is an options object it can have the properties `async`, `call`, `continuable`, `pathRouter`, and `strict`. If it is a boolean, it is used as the value of `strict`. 124 | 125 | If `async` is true, then the case tests and actions can be asynchronous and their return values will be awaited. 126 | 127 | If `call` is `true` and a property value is a function, it will be called with the switching value and the result returned. This can be very powerful when used with pattern matching destructuring. You do not need to set this if you explicity set `pathRouter` (see below) or call `route(test,handler)` on your switch. 128 | 129 | If `continuable` is `true`, `call` is set to `true` and any functions that return undefined cascade to the next case. This is useful for adding logging and similar capability for router like functionality. You do not need to set this if you explicity set `pathRouter` (see below) or call `route(test,handler)` on your switch. 130 | 131 | If `strict` is `true`, all property values in the case object that can be converted to integers will only match integers. Otherwise, a soft compare is done and "1" will equal 1. 132 | 133 | ```javascript 134 | const sw = switchcase({},{continuable:true}) 135 | .case(()=>true,(value) => console.log(value)) 136 | .case(1,value => value) 137 | .case(2,value => value * 2) 138 | ``` 139 | 140 | `pathRouter` is used to turn a switchcase into a router. See the section on [routing](#router). 141 | 142 | With the exceptions of `continuable` and `pathRouter`, if you want to defer the resolution type, the same second argument can be provided to the invocation of a `switchcase` to override those provided when the switch was created, e.g. 143 | 144 | ```javascript 145 | const sw = switchcase({1:"case 1"}); 146 | 147 | sw(1,{strict:true}); 148 | ``` 149 | 150 | ## Pattern Matching 151 | 152 | You can use partial objects in cases to match objects: 153 | 154 | ```javascript 155 | const sw = switchcase(); 156 | 157 | sw.case({address: {city: "Seattle"}},({name}) => name); 158 | 159 | sw({name:"joe",address:{city: "Seattle"}},{call:true})); // returns "joe" 160 | ``` 161 | 162 | If you want to use patterns with object based switching, you will need to stringify them, e.g. 163 | 164 | 165 | ```javascript 166 | const sw = switchcase({ 167 | [JSON.stringify({address: {city: "Seattle"}})]: ({name}) => name 168 | }); 169 | 170 | ``` 171 | 172 | You might also want to explore the use of functional switches: 173 | 174 | ```javascript 175 | const sw = switchcase({ 176 | [({address: {city}}) => city==="Seattle"]: ({name}) => name; 177 | }); 178 | ``` 179 | 180 | Or, functional matching: 181 | 182 | ```javascript 183 | const sw = switchcase(); 184 | 185 | sw.case({address: {city: value => value==="Seattle"}},({name}) => name); 186 | 187 | sw({name:"joe",address:{city: "Seattle"}},{call:true,functionalMatch:true})); // returns "joe" 188 | ``` 189 | 190 | And even, reverse functional matching: 191 | 192 | ```javascript 193 | const sw = switchcase(); 194 | 195 | sw.case({address: {city: "Seattle"}},({name}) => name); 196 | 197 | sw({name:"joe",address:{city: value => value==="Seattle"}},{call:true,functionalMatch:true})); // returns "joe" 198 | ``` 199 | 200 | Reverse functional matching is leveraged to support [object selection](#object-selection). 201 | 202 | Also, anywhere you use a function in functional switches or functional matching, you can use a regular expression. 203 | 204 |   205 | 206 | ## Object Selection 207 | 208 | If you pass in an iterable (usually Array) of values, then `switchcase` can be used to return the subset of values that match a pattern. The below will return the object with the name "joe". 209 | 210 | ```javascript 211 | switchcase([ 212 | {name:"joe",age:21,address:{city:"Seattle",zipcode:"98101"}}, 213 | {name:"mary",age:20,address:{city:"Seattle",zipcode:"90101"}}, 214 | {name:"joan",age:22,address:{city:"Bainbridge Island",zipcode:"98110"}]]) 215 | .match({age:(value) => value >=21,address:{city:"Seattle"}}); 216 | ``` 217 | 218 |   219 | 220 | ## Path Router 221 | 222 | You can use `switchcase` like a regular router if you create your switch with the `pathRouter` option or ever call `.route(test,handler)` instead of `case(test,value)`. 223 | 224 | `switchcase` will automatically handle requests in any of these forms: 225 | 226 | ```javascript 227 | {request:{path: },response:} 228 | 229 | {request:{location: },response:} 230 | 231 | {request:{url: },response:} 232 | 233 | {req:{path: },res:} 234 | 235 | {req:{location: },res:} 236 | 237 | {req:{url: },res:} 238 | 239 | {newURL: } // matches a hashchange browser event 240 | 241 | {path: },...} 242 | 243 | {location: },...} 244 | 245 | {url: },...} 246 | 247 | {path: },...} 248 | 249 | ``` 250 | 251 | If a `pathRouter` configuration object is not passed in, then `request` objects or the `hashchange` events are automatically enhanced to have the properties shown below if they don't already exist and parameterized paths with colon delimiters are automatically handled, e.g. `/:id/:version/`. This maximizes similarity to routers from other libraries. 252 | 253 | 254 | ```javascript 255 | { 256 | path: , 257 | pathname: , 258 | location: [a URL object](https://developer.mozilla.org/en-US/docs/Web/API/URL), // if a url string existed 259 | url: // if location existed 260 | params: {:[,...} // if a parameterized path was matched 261 | } 262 | ``` 263 | 264 | Alternatively, an `pathRouter` configuration object can be provided to look-up the path for matching and set parameters when a parameterized path is encountered: 265 | 266 | ```javascript 267 | { 268 | pathRouter: 269 | { 270 | route: object => object.URL.pathname 271 | setParams: (object,params) => object.args = params; 272 | } 273 | } 274 | ``` 275 | 276 | When using `switchcase` as a router, any routed functions that return a value other than `undefined` are considered to have succeeded, routing stops and switchcase returns the value. If a routed function returns `undefined`, routing continues. 277 | 278 | ### Path Router Example 279 | 280 | ```javascript 281 | function bodyparser({req}) { 282 | ... ... 283 | return; 284 | } 285 | 286 | const sw = switchcase() 287 | .route(()=>true,bodyparser) 288 | .route("/:id/:name",({req,res}) => { 289 | console.log(req.params,req.path); 290 | return true; 291 | }) 292 | .route("/login/",({req,res}) => { 293 | console.log(req.params,req.path); 294 | return true; 295 | }) 296 | .default({req} => { 297 | console.log("Not Found",req.params,req.path); 298 | }); 299 | sw.handle({req:{url:"https://www.somesite.com/1/joe"},res:{}}); 300 | ``` 301 | 302 | 303 | # Internals 304 | 305 | Some of the power of `switchcase` comes from the use of the ES2015 object initializer syntax, `[]`. This allows the use of the JavaScript interpreter to validate functions and regular expressions before they get turned into string property names. The `switchcase` function then loops through the properties in the object and converts them back to functions or regular expressions. The rest of the power comes from object destructuring. 306 | 307 | # Rational and Background 308 | 309 | There are a number of articles on the use of decalarative or functional approaches to the replacement of switch. In our opinion, here are three of the best: 310 | 311 | 1) July, 2014 [Replacing switch statements with object literals](https://toddmotto.com/deprecating-the-switch-statement-for-object-literals/) ... plus commentary on [Reddit](http://www.reddit.com/r/javascript/comments/2b4s6r/deprecating_the_switch_statement_for_object). 312 | 313 | 2) Jan, 2017 [Rethinking JavaScript: Eliminate the switch statement for better code](https://hackernoon.com/rethinking-javascript-eliminate-the-switch-statement-for-better-code-5c81c044716d) 314 | 315 | 3) Nov, 2017 [Alternative to JavaScript’s switch statement with a functional twist](https://codeburst.io/alternative-to-javascripts-switch-statement-with-a-functional-twist-3f572787ba1c) 316 | 317 | We simply wanted a switch capability that could support literals, functional tests, regular expressions, and object patterns so that we could build super flexible routers. 318 | 319 | # Release History - Reverse Chronological Order 320 | 321 | 2019-02-13 v1.0.11 Addressed [issue 4](https://github.com/anywhichway/switchcase/issues/4) 322 | 323 | 2019-02-12 v1.0.10 Removed an extra await. 324 | 325 | 2019-02-12 v1.0.9 Added async support. See recursion.html example. 326 | 327 | 2019-02-11 v1.0.8 Patched incomplete example. 328 | 329 | 2019-02-11 v1.0.7 Tested switch recursion and added ability to pass args during recursion. See recursion.html example. 330 | 331 | 2019-02-09 v1.0.6 Corrected issue with pattern matched objects getting frozen. 332 | 333 | 2019-02-09 v1.0.5 Optimized switch creation for large arrays. Can also now pass in a true iterable. 334 | 335 | 2019-02-09 v1.0.4 Added object selection. Enhanced routing with some naming aliases/sugar. 336 | 337 | 2019-02-08 v1.0.3 Added array initialization of switch and the `pathRouter` option. 338 | 339 | 2019-02-06 v1.0.2 Added support for functional object patterns. The values in pattern properties can be functions or RegExp used to test the value passed into the switch. Alternatively, the values passed in to the switch can be used to reverse match to the switch. 340 | 341 | 2019-02-05 v1.0.1 Added support for object pattern matching, ability to call switch values, case continuation, and deferring the strict constraint to switch evaluation time. 342 | 343 | 2018-04-13 v1.0.0 Code style improvements. Fixed node.js unit test config. 344 | 345 | 2017-11-27 v0.0.3 Added unit tests 346 | 347 | 2017-11-27 v0.0.2 Added documentation 348 | 349 | 2017-11-27 v0.0.1 First public release 350 | -------------------------------------------------------------------------------- /browser/switchcase.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { 5 | const type = typeof(value); 6 | if(typeof(pattern)!=="object" && type!=="object") { 7 | if(value===pattern) { return true; } 8 | if(routing && type==="string" && typeof(pattern)==="string") { 9 | const pparts = pattern.split("/"), 10 | vparts = value.split("/"); 11 | if(pparts[0]==="") { pparts.shift(); } 12 | if(vparts[0]==="") { vparts.shift(); } 13 | let part; 14 | if(pparts.length!==vparts.length) { 15 | return false; 16 | } 17 | while(pparts.length>0) { 18 | const ppart = pparts.shift(), 19 | vpart = vparts.shift(); 20 | if(ppart[0]===":") { 21 | if(!routing.params) { 22 | routing.params = {}; 23 | } 24 | let value = vpart; 25 | try { 26 | value = JSON.parse(value); 27 | } catch(e) { 28 | true; 29 | } 30 | routing.params[ppart.substring(1)] = value; 31 | } else if(vpart!==ppart) { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | } 38 | if(pattern instanceof Date) { 39 | if(value instanceof Date) { 40 | return pattern.getTime()===value.getTime(); 41 | } 42 | return false; 43 | } 44 | if(pattern instanceof RegExp) { 45 | if(["boolean","number","string"].includes(type)) { 46 | return pattern.test(value); 47 | } 48 | return false; 49 | } 50 | if(pattern instanceof Function) { 51 | return !!pattern(value); 52 | } 53 | return Object.keys(pattern).every((key) => { 54 | let pvalue = pattern[key], 55 | ptype = typeof(pvalue), 56 | test = (value) => value===pvalue; 57 | if(ptype==="undefined" || (pvalue && ptype==="object" && Object.keys(pvalue).length===0)) { 58 | return true; 59 | } 60 | if(key.startsWith("/")) { 61 | const i = key.lastIndexOf("/"); 62 | if(i>0) { 63 | try { 64 | const regexp = new RegExp(key.substring(1,i),key.substring(i+1)); 65 | test = (key) => regexp.test(key); 66 | } catch(e) { 67 | true; 68 | } 69 | } 70 | } else if(key.includes("=>")) { 71 | try { 72 | test = Function("return " + key)(); 73 | } catch(e) { 74 | true; 75 | } 76 | } 77 | return Object.keys(value).every((vkey) => { 78 | let vtest = () => vkey===key; 79 | if(vkey.startsWith("/")) { 80 | const i = vkey.lastIndexOf("/"); 81 | if(i>0) { 82 | try { 83 | const regexp = new RegExp(vkey.substring(1,i),vkey.substring(i+1)); 84 | vtest = regexp.test(key); 85 | } catch(e) { 86 | true; 87 | } 88 | } 89 | } else if(vkey.includes("=>")) { 90 | try { 91 | vtest = Function("return " + vkey)(); 92 | } catch(e) { 93 | true; 94 | } 95 | } 96 | if(test(vkey) || vtest()) { 97 | const vvalue = value[vkey], 98 | vtype = typeof(vvalue); 99 | if(functional && ptype==="function") { 100 | return pvalue(vvalue); 101 | } 102 | if(pvalue && ptype==="object" && vtype==="object") { 103 | return vvalue ? matches(vvalue,pvalue,functional) : false; 104 | } 105 | return pvalue===vvalue; 106 | } 107 | return true; 108 | }); 109 | }); 110 | }, 111 | deepFreeze = (data) => { 112 | if(data && typeof(data)==="object") { 113 | Object.freeze(data); 114 | Object.keys(data).forEach((key) => deepFreeze(data[key])); 115 | } 116 | return data; 117 | }, 118 | getPath = (object) => { 119 | if(object.path) { 120 | return object.path; 121 | } 122 | if(object.location && object.location.pathname) { 123 | return object.path = object.location.pathname; 124 | } 125 | if(object.url || object.URL || object.newURL) { 126 | if(!object.location) { 127 | object.location = new URL(object.url || object.URL || object.newURL); 128 | } 129 | if(object.location.pathname) { 130 | return object.path = object.location.pathname; 131 | } 132 | return object.path = new URL(object.url || object.URL || object.newURL).pathname; 133 | } 134 | }, 135 | switchcase = (cases={},defaults={}) => { 136 | let switches = []; 137 | if(defaults && typeof(defaults)!=="object") { 138 | defaults = {strict:defaults}; 139 | } 140 | if(cases!=null && typeof cases[Symbol.iterator] === "function") { 141 | switches = cases.slice(); 142 | } else { 143 | Object.keys(cases).forEach((key) => { 144 | let test = key; 145 | try { 146 | test = Function("return " + key)(); 147 | } catch(e) { true; } 148 | switches.push([test,cases[key]]); 149 | }); 150 | } 151 | const switcher = Function("defaults","switches","matches","deepFreeze","getPath","switcher", 152 | `return ${defaults.async ? "async " : ""}(value,options={},...args) => { 153 | delete options.pathRouter; 154 | delete options.continuable; 155 | options = Object.assign({},defaults,options); 156 | if(options.pathRouter) { options.continuable = true; } 157 | if(options.continuable) { options.call = true; } 158 | let target = value, 159 | setParams; 160 | if(options.pathRouter) { 161 | const type = typeof(options.pathRouter.route); 162 | if(type==="function") { 163 | target = options.pathRouter.route(value); 164 | } else if(type==="string") { 165 | target = value[options.pathRouter.route]; 166 | } else if(value.req) { 167 | target = getPath(value.req); 168 | } else if(value.request) { 169 | target = getPath(value.request); 170 | } else { 171 | target = getPath(value); 172 | } 173 | setParams = options.pathRouter.setParams; 174 | if(!setParams) { 175 | setParams = (value,params) => { 176 | if(value.req) { 177 | value.req.params = Object.assign({},value.req.params,params); 178 | } else if(value.request) { 179 | value.request.params = Object.assign({},value.request.params,params); 180 | } else { 181 | value.params = Object.assign({},value.params,params); 182 | } 183 | }; 184 | } 185 | } 186 | const routing = options.pathRouter ? {} : null; 187 | let results; // for collecting items when using as an object matcher 188 | for(let item of switches) { 189 | const key = Array.isArray(item) ? item[0] : item, 190 | type = typeof(key); 191 | let pattern = key, 192 | result = item[1]; 193 | if(key===item) { // swap target and pattern if using for object matching 194 | target = key; 195 | pattern = value; 196 | } 197 | if(key && key!==item && type==="object" && !Object.isFrozen(key)) { // doesn't check deep but good enough and fast 198 | deepFreeze(key); 199 | } 200 | if((key && (type==="object" || routing) && matches(target,pattern,result===undefined ? true : options.functionalMatch,routing)) 201 | || (result!==undefined && type==="function" && ${defaults.async ? "await " : ""} key(target,...args)) 202 | || (result!==undefined && options.strict && key===target) 203 | || (result!==undefined && !options.strict && key==target)) { 204 | if(result===undefined) { // case is an object to match 205 | if(!results) { results = []; } 206 | results.push(target); 207 | continue; 208 | } 209 | if(typeof(result)==="function" && options.call) { 210 | if(setParams && routing.params) { 211 | setParams(value,routing.params); 212 | } 213 | const resolved = ${defaults.async ? "await " : ""} result(value,...args); 214 | if(resolved!==undefined || !options.continuable) { return resolved; } 215 | if(options.continuable) { continue; } 216 | result = resolved; 217 | } 218 | return result; 219 | } 220 | } 221 | const result = options.call && typeof(switcher().otherwise)==="function" ? ${defaults.async ? "await " : ""} switcher().otherwise(value) : switcher().otherwise; 222 | return result === undefined ? results : result; 223 | }` 224 | )(defaults,switches,matches,deepFreeze,getPath,() => switcher); 225 | switcher.otherwise = cases.default; 226 | switcher.case = (test,value) => { 227 | switches.push([test,value]); 228 | return switcher; 229 | }; 230 | switcher.route = (test,value) => { 231 | defaults.pathRouter = true; 232 | switches.push([test,value]); 233 | return switcher; 234 | }; 235 | switcher.default = (value) => { 236 | switcher.otherwise = value; 237 | return switcher; 238 | }; 239 | switcher.match = (value) => switcher(value); 240 | switcher.handle = switcher.match; 241 | return switcher; 242 | } 243 | if(typeof(module)!=="undefined") { 244 | module.exports = switchcase; 245 | } 246 | if(typeof(window)!=="undefined") { 247 | window.switchcase = switchcase; 248 | } 249 | }()); 250 | },{}]},{},[1]); 251 | -------------------------------------------------------------------------------- /browser/switchcase.min.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o{const type=typeof value;if(typeof pattern!=="object"&&type!=="object"){if(value===pattern){return true}if(routing&&type==="string"&&typeof pattern==="string"){const pparts=pattern.split("/"),vparts=value.split("/");if(pparts[0]===""){pparts.shift()}if(vparts[0]===""){vparts.shift()}let part;if(pparts.length!==vparts.length){return false}while(pparts.length>0){const ppart=pparts.shift(),vpart=vparts.shift();if(ppart[0]===":"){if(!routing.params){routing.params={}}let value=vpart;try{value=JSON.parse(value)}catch(e){true}routing.params[ppart.substring(1)]=value}else if(vpart!==ppart){return false}}return true}}if(pattern instanceof Date){if(value instanceof Date){return pattern.getTime()===value.getTime()}return false}if(pattern instanceof RegExp){if(["boolean","number","string"].includes(type)){return pattern.test(value)}return false}if(pattern instanceof Function){return!!pattern(value)}return Object.keys(pattern).every(key=>{let pvalue=pattern[key],ptype=typeof pvalue,test=value=>value===pvalue;if(ptype==="undefined"||pvalue&&ptype==="object"&&Object.keys(pvalue).length===0){return true}if(key.startsWith("/")){const i=key.lastIndexOf("/");if(i>0){try{const regexp=new RegExp(key.substring(1,i),key.substring(i+1));test=(key=>regexp.test(key))}catch(e){true}}}else if(key.includes("=>")){try{test=Function("return "+key)()}catch(e){true}}return Object.keys(value).every(vkey=>{let vtest=()=>vkey===key;if(vkey.startsWith("/")){const i=vkey.lastIndexOf("/");if(i>0){try{const regexp=new RegExp(vkey.substring(1,i),vkey.substring(i+1));vtest=regexp.test(key)}catch(e){true}}}else if(vkey.includes("=>")){try{vtest=Function("return "+vkey)()}catch(e){true}}if(test(vkey)||vtest()){const vvalue=value[vkey],vtype=typeof vvalue;if(functional&&ptype==="function"){return pvalue(vvalue)}if(pvalue&&ptype==="object"&&vtype==="object"){return vvalue?matches(vvalue,pvalue,functional):false}return pvalue===vvalue}return true})})},deepFreeze=data=>{if(data&&typeof data==="object"){Object.freeze(data);Object.keys(data).forEach(key=>deepFreeze(data[key]))}return data},getPath=object=>{if(object.path){return object.path}if(object.location&&object.location.pathname){return object.path=object.location.pathname}if(object.url||object.URL||object.newURL){if(!object.location){object.location=new URL(object.url||object.URL||object.newURL)}if(object.location.pathname){return object.path=object.location.pathname}return object.path=new URL(object.url||object.URL||object.newURL).pathname}},switchcase=(cases={},defaults={})=>{let switches=[];if(defaults&&typeof defaults!=="object"){defaults={strict:defaults}}if(cases!=null&&typeof cases[Symbol.iterator]==="function"){switches=cases.slice()}else{Object.keys(cases).forEach(key=>{let test=key;try{test=Function("return "+key)()}catch(e){true}switches.push([test,cases[key]])})}const switcher=Function("defaults","switches","matches","deepFreeze","getPath","switcher",`return ${defaults.async?"async ":""}(value,options={},...args) => {\n\tdelete options.pathRouter;\n\tdelete options.continuable;\n\toptions = Object.assign({},defaults,options);\n\tif(options.pathRouter) { options.continuable = true; }\n\tif(options.continuable) { options.call = true; }\n\tlet target = value,\n\t\tsetParams;\n\tif(options.pathRouter) {\n\t\tconst type = typeof(options.pathRouter.route);\n\t\tif(type==="function") {\n\t\t\ttarget = options.pathRouter.route(value);\n\t\t} else if(type==="string") {\n\t\t\ttarget = value[options.pathRouter.route];\n\t\t} else if(value.req) {\n\t\t\ttarget = getPath(value.req);\n\t\t} else if(value.request) {\n\t\t\ttarget = getPath(value.request);\n\t\t} else {\n\t\t\ttarget = getPath(value);\n\t\t}\n\t\tsetParams = options.pathRouter.setParams;\n\t\tif(!setParams) {\n\t\t\tsetParams = (value,params) => {\n\t\t\t\tif(value.req) {\n\t\t\t\t\tvalue.req.params = Object.assign({},value.req.params,params);\n\t\t\t\t} else if(value.request) {\n\t\t\t\t\tvalue.request.params = Object.assign({},value.request.params,params);\n\t\t\t\t} else {\n\t\t\t\t\tvalue.params = Object.assign({},value.params,params);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t}\n\tconst routing = options.pathRouter ? {} : null;\n\tlet results; // for collecting items when using as an object matcher\n\tfor(let item of switches) {\n\t\tconst key = Array.isArray(item) ? item[0] : item,\n\t\t\ttype = typeof(key);\n\t\tlet pattern = key,\n\t\t\tresult = item[1];\n\t\tif(key===item) { // swap target and pattern if using for object matching\n\t\t\ttarget = key;\n\t\t\tpattern = value;\n\t\t}\n\t\tif(key && key!==item && type==="object" && !Object.isFrozen(key)) { // doesn't check deep but good enough and fast\n\t\t\tdeepFreeze(key);\n\t\t}\n\t\tif((key && (type==="object" || routing) && matches(target,pattern,result===undefined ? true : options.functionalMatch,routing))\n\t\t\t\t|| (result!==undefined && type==="function" && ${defaults.async?"await ":""} key(target,...args))\n\t\t\t\t|| (result!==undefined && options.strict && key===target)\n\t\t\t\t|| (result!==undefined && !options.strict && key==target))\t{\n\t\t\tif(result===undefined) { // case is an object to match\n\t\t\t\tif(!results) { results = []; }\n\t\t\t\tresults.push(target);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif(typeof(result)==="function" && options.call) {\n\t\t\t\tif(setParams && routing.params) {\n\t\t\t\t\tsetParams(value,routing.params);\n\t\t\t\t}\n\t\t\t\tconst resolved = ${defaults.async?"await ":""} result(value,...args);\n\t\t\t\tif(resolved!==undefined || !options.continuable) { return resolved; }\n\t\t\t\tif(options.continuable) { continue; }\n\t\t\t\tresult = resolved;\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\tconst result = options.call && typeof(switcher().otherwise)==="function" ? ${defaults.async?"await ":""} switcher().otherwise(value) : switcher().otherwise;\n\treturn result === undefined ? results : result;\n}`)(defaults,switches,matches,deepFreeze,getPath,()=>switcher);switcher.otherwise=cases.default;switcher.case=((test,value)=>{switches.push([test,value]);return switcher});switcher.route=((test,value)=>{defaults.pathRouter=true;switches.push([test,value]);return switcher});switcher.default=(value=>{switcher.otherwise=value;return switcher});switcher.match=(value=>switcher(value));switcher.handle=switcher.match;return switcher};if(typeof module!=="undefined"){module.exports=switchcase}if(typeof window!=="undefined"){window.switchcase=switchcase}})()},{}]},{},[1]); -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 34 | 35 | -------------------------------------------------------------------------------- /examples/recursion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 109 | 110 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | const matches = (value,pattern,functional,routing) => { 4 | const type = typeof(value); 5 | if(typeof(pattern)!=="object" && type!=="object") { 6 | if(value===pattern) { return true; } 7 | if(routing && type==="string" && typeof(pattern)==="string") { 8 | const pparts = pattern.split("/"), 9 | vparts = value.split("/"); 10 | if(pparts[0]==="") { pparts.shift(); } 11 | if(vparts[0]==="") { vparts.shift(); } 12 | let part; 13 | if(pparts.length!==vparts.length) { 14 | return false; 15 | } 16 | while(pparts.length>0) { 17 | const ppart = pparts.shift(), 18 | vpart = vparts.shift(); 19 | if(ppart[0]===":") { 20 | if(!routing.params) { 21 | routing.params = {}; 22 | } 23 | let value = vpart; 24 | try { 25 | value = JSON.parse(value); 26 | } catch(e) { 27 | true; 28 | } 29 | routing.params[ppart.substring(1)] = value; 30 | } else if(vpart!==ppart) { 31 | return false; 32 | } 33 | } 34 | return true; 35 | } 36 | } 37 | if(pattern instanceof Date) { 38 | if(value instanceof Date) { 39 | return pattern.getTime()===value.getTime(); 40 | } 41 | return false; 42 | } 43 | if(pattern instanceof RegExp) { 44 | if(["boolean","number","string"].includes(type)) { 45 | return pattern.test(value); 46 | } 47 | return false; 48 | } 49 | if(pattern instanceof Function) { 50 | return !!pattern(value); 51 | } 52 | return Object.keys(pattern).every((key) => { 53 | let pvalue = pattern[key], 54 | ptype = typeof(pvalue), 55 | test = (value) => value===pvalue; 56 | if(ptype==="undefined" || (pvalue && ptype==="object" && Object.keys(pvalue).length===0)) { 57 | return true; 58 | } 59 | if(key.startsWith("/")) { 60 | const i = key.lastIndexOf("/"); 61 | if(i>0) { 62 | try { 63 | const regexp = new RegExp(key.substring(1,i),key.substring(i+1)); 64 | test = (key) => regexp.test(key); 65 | } catch(e) { 66 | true; 67 | } 68 | } 69 | } else if(key.includes("=>")) { 70 | try { 71 | test = Function("return " + key)(); 72 | } catch(e) { 73 | true; 74 | } 75 | } 76 | return Object.keys(value).every((vkey) => { 77 | let vtest = () => vkey===key; 78 | if(vkey.startsWith("/")) { 79 | const i = vkey.lastIndexOf("/"); 80 | if(i>0) { 81 | try { 82 | const regexp = new RegExp(vkey.substring(1,i),vkey.substring(i+1)); 83 | vtest = regexp.test(key); 84 | } catch(e) { 85 | true; 86 | } 87 | } 88 | } else if(vkey.includes("=>")) { 89 | try { 90 | vtest = Function("return " + vkey)(); 91 | } catch(e) { 92 | true; 93 | } 94 | } 95 | if(test(vkey) || vtest()) { 96 | const vvalue = value[vkey], 97 | vtype = typeof(vvalue); 98 | if(functional && ptype==="function") { 99 | return pvalue(vvalue); 100 | } 101 | if(pvalue && ptype==="object" && vtype==="object") { 102 | return vvalue ? matches(vvalue,pvalue,functional) : false; 103 | } 104 | return pvalue===vvalue; 105 | } 106 | return true; 107 | }); 108 | }); 109 | }, 110 | deepFreeze = (data) => { 111 | if(data && typeof(data)==="object") { 112 | Object.freeze(data); 113 | Object.keys(data).forEach((key) => deepFreeze(data[key])); 114 | } 115 | return data; 116 | }, 117 | getPath = (object) => { 118 | if(object.path) { 119 | return object.path; 120 | } 121 | if(object.location && object.location.pathname) { 122 | return object.path = object.location.pathname; 123 | } 124 | if(object.url || object.URL || object.newURL) { 125 | if(!object.location) { 126 | object.location = new URL(object.url || object.URL || object.newURL); 127 | } 128 | if(object.location.pathname) { 129 | return object.path = object.location.pathname; 130 | } 131 | return object.path = new URL(object.url || object.URL || object.newURL).pathname; 132 | } 133 | }, 134 | switchcase = (cases={},defaults={}) => { 135 | let switches = []; 136 | if(defaults && typeof(defaults)!=="object") { 137 | defaults = {strict:defaults}; 138 | } 139 | if(cases!=null && typeof cases[Symbol.iterator] === "function") { 140 | switches = cases.slice(); 141 | } else { 142 | Object.keys(cases).forEach((key) => { 143 | let test = key; 144 | try { 145 | test = Function("return " + key)(); 146 | } catch(e) { true; } 147 | switches.push([test,cases[key]]); 148 | }); 149 | } 150 | const switcher = Function("defaults","switches","matches","deepFreeze","getPath","switcher", 151 | `return ${defaults.async ? "async " : ""}(value,options={},...args) => { 152 | delete options.pathRouter; 153 | delete options.continuable; 154 | options = Object.assign({},defaults,options); 155 | if(options.pathRouter) { options.continuable = true; } 156 | if(options.continuable) { options.call = true; } 157 | let target = value, 158 | setParams; 159 | if(options.pathRouter) { 160 | const type = typeof(options.pathRouter.route); 161 | if(type==="function") { 162 | target = options.pathRouter.route(value); 163 | } else if(type==="string") { 164 | target = value[options.pathRouter.route]; 165 | } else if(value.req) { 166 | target = getPath(value.req); 167 | } else if(value.request) { 168 | target = getPath(value.request); 169 | } else { 170 | target = getPath(value); 171 | } 172 | setParams = options.pathRouter.setParams; 173 | if(!setParams) { 174 | setParams = (value,params) => { 175 | if(value.req) { 176 | value.req.params = Object.assign({},value.req.params,params); 177 | } else if(value.request) { 178 | value.request.params = Object.assign({},value.request.params,params); 179 | } else { 180 | value.params = Object.assign({},value.params,params); 181 | } 182 | }; 183 | } 184 | } 185 | const routing = options.pathRouter ? {} : null; 186 | let results; // for collecting items when using as an object matcher 187 | for(let item of switches) { 188 | const key = Array.isArray(item) ? item[0] : item, 189 | type = typeof(key); 190 | let pattern = key, 191 | result = item[1]; 192 | if(key===item) { // swap target and pattern if using for object matching 193 | target = key; 194 | pattern = value; 195 | } 196 | if(key && key!==item && type==="object" && !Object.isFrozen(key)) { // doesn't check deep but good enough and fast 197 | deepFreeze(key); 198 | } 199 | if((key && (type==="object" || routing) && matches(target,pattern,result===undefined ? true : options.functionalMatch,routing)) 200 | || (result!==undefined && type==="function" && ${defaults.async ? "await " : ""} key(target,...args)) 201 | || (result!==undefined && options.strict && key===target) 202 | || (result!==undefined && !options.strict && key==target)) { 203 | if(result===undefined) { // case is an object to match 204 | if(!results) { results = []; } 205 | results.push(target); 206 | continue; 207 | } 208 | if(typeof(result)==="function" && options.call) { 209 | if(setParams && routing.params) { 210 | setParams(value,routing.params); 211 | } 212 | const resolved = ${defaults.async ? "await " : ""} result(value,...args); 213 | if(resolved!==undefined || !options.continuable) { return resolved; } 214 | if(options.continuable) { continue; } 215 | result = resolved; 216 | } 217 | return result; 218 | } 219 | } 220 | const result = options.call && typeof(switcher().otherwise)==="function" ? ${defaults.async ? "await " : ""} switcher().otherwise(value) : switcher().otherwise; 221 | return result === undefined ? results : result; 222 | }` 223 | )(defaults,switches,matches,deepFreeze,getPath,() => switcher); 224 | switcher.otherwise = cases.default; 225 | switcher.case = (test,value) => { 226 | switches.push([test,value]); 227 | return switcher; 228 | }; 229 | switcher.route = (test,value) => { 230 | defaults.pathRouter = true; 231 | switches.push([test,value]); 232 | return switcher; 233 | }; 234 | switcher.default = (value) => { 235 | switcher.otherwise = value; 236 | return switcher; 237 | }; 238 | switcher.match = (value) => switcher(value); 239 | switcher.handle = switcher.match; 240 | return switcher; 241 | } 242 | if(typeof(module)!=="undefined") { 243 | module.exports = switchcase; 244 | } 245 | if(typeof(window)!=="undefined") { 246 | window.switchcase = switchcase; 247 | } 248 | }()); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "switchcase", 3 | "version": "1.0.11", 4 | "description": "Declarative and functional switch supporting literals, functional tests, regular expressions, object patterns and routes", 5 | "engines": {}, 6 | "license": "MIT", 7 | "scripts": { 8 | "prepare": "browserify index.js -o browser/switchcase.js && uglifyjs browser/switchcase.js -o browser/switchcase.min.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/anywhichway/switchcase.git" 13 | }, 14 | "keywords": [ 15 | "switch", 16 | "case" 17 | ], 18 | "author": "Simon Y. Blackwell (http://www.github.com/anywhichway)", 19 | "bugs": { 20 | "url": "https://github.com/anywhichway/switchcase/issues" 21 | }, 22 | "homepage": "https://github.com/anywhichway/switchcase#readme", 23 | "devDependencies": { 24 | "benchmark": "^2.1.3", 25 | "blanket": "^1.2.3", 26 | "chai": "^3.4.1", 27 | "codacy-coverage": "^2.0.0", 28 | "codeclimate-test-reporter": "^0.2.0", 29 | "istanbul": "^0.4.2", 30 | "mocha": "^2.3.4", 31 | "uglify-es": "^3.1.6" 32 | }, 33 | "dependencies": {} 34 | } 35 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var chai, 2 | expect, 3 | unionizor, 4 | _; 5 | if(typeof(window)==="undefined") { 6 | chai = require("chai"); 7 | expect = chai.expect; 8 | switchcase = require("../index.js"); 9 | } 10 | 11 | let sw1 = switchcase({ 12 | 1: "Case one, a literal", 13 | [(value) => value===2]: "Case two, a function", 14 | [/3/]: "Case three, a regular expression", 15 | [JSON.stringify({address: {city: "Seattle"}})]: ({name}) => console.log(`Case four, object pattern matching, name is ${name}`), 16 | default: "Defaulted" 17 | }); 18 | 19 | describe("Test",function() { 20 | it("literal",function() { 21 | let sw = switchcase({ 22 | 1: "1" 23 | }); 24 | expect(sw(1)).to.equal("1"); 25 | }); 26 | it("function",function() { 27 | let sw = switchcase({ 28 | [(value) => value===1]: "1", 29 | }); 30 | expect(sw(1)).to.equal("1"); 31 | }); 32 | it("RegExp",function() { 33 | let sw = switchcase({ 34 | [/1/]: "1" 35 | }); 36 | expect(sw(1)).to.equal("1"); 37 | }); 38 | it("pattern matching",function() { 39 | let sw = switchcase({ 40 | [JSON.stringify({address: {city: "Seattle"}})]: ({name}) => name 41 | },{call:true}); 42 | expect(sw({name:"joe",address:{city: "Seattle"}})).to.equal("joe"); 43 | }); 44 | it("pattern matching array init",function() { 45 | let sw = switchcase([ 46 | [{address: {city: "Seattle"}},({name}) => name] 47 | ],{call:true}); 48 | expect(sw({name:"joe",address:{city: "Seattle"}})).to.equal("joe"); 49 | }); 50 | it("default",function() { 51 | let sw = switchcase({ 52 | [/1/]: "1", 53 | default: "1" 54 | }); 55 | expect(sw(2)).to.equal("1"); 56 | }); 57 | it("case literal",function() { 58 | let sw = switchcase(); 59 | sw.case(1,"1"); 60 | expect(sw(1)).to.equal("1"); 61 | }); 62 | it("case pattern",function() { 63 | let sw = switchcase({}); 64 | sw.case({address: {city: "Seattle"}},({name}) => name) 65 | expect(sw({name:"joe",address:{city: "Seattle"}},{call:true})).to.equal("joe"); 66 | }); 67 | it("case pattern functional",function() { 68 | let sw = switchcase({}); 69 | sw.case({address: {city: value => value==="Seattle"}},({name}) => name) 70 | expect(sw({name:"joe",address:{city: "Seattle"}},{call:true,functionalMatch:true})).to.equal("joe"); 71 | }); 72 | it("case pattern RegExp",function() { 73 | let sw = switchcase({}); 74 | sw.case({address: {city: /Seattle/g}},({name}) => name) 75 | expect(sw({name:"joe",address:{city: "Seattle"}},{call:true,functionalMatch:true})).to.equal("joe"); 76 | }); 77 | it("case pattern key test",function() { 78 | let sw = switchcase({}); 79 | sw.case({address: {city: "Seattle"}},({name}) => name) 80 | expect(sw({name:"joe",[key => key==="address"]:{city: "Seattle"}},{call:true})).to.equal("joe"); 81 | }); 82 | it("case key test",function() { 83 | let sw = switchcase({}); 84 | sw.case({[key => key==="address"]: {city: "Seattle"}},({name}) => name) 85 | expect(sw({name:"joe","address":{city: "Seattle"}},{call:true})).to.equal("joe"); 86 | }); 87 | it("case destructuring",function() { 88 | const sw = switchcase({ 89 | [({address: {city}}) => city==="Seattle"]: ({name}) => name 90 | }); 91 | expect(sw({name:"joe",address:{city: "Seattle"}},{call:true})).to.equal("joe"); 92 | }); 93 | it("case default",function() { 94 | let sw = switchcase({}); 95 | sw.default("1"); 96 | expect(sw(2)).to.equal("1"); 97 | }); 98 | it("continuation",function() { 99 | let logged, 100 | sw = switchcase({},{continuable:true}); 101 | sw.case(()=>true,value => { logged=value; }) 102 | .default("1"); 103 | expect(sw(2)).to.equal("1"); 104 | expect(logged).to.equal(2); 105 | }); 106 | it("route",function() { 107 | let logged, 108 | router = switchcase() 109 | .route("/:id/:name",({req:{params:{id,name}}}) => { 110 | logged = id; 111 | console.log(`You requested the object with id ${id} and name ${name}`); 112 | }); 113 | router.handle({req:{url:"https://www.somesite.com/1/joe"},res:{}}); 114 | expect(logged!==undefined).equal(true); 115 | }) 116 | it("route custom location",function() { 117 | let logged, 118 | router = switchcase({},{pathRouter:{route:object => new URL(object.req.url).pathname}}) 119 | .route("/:id/:name",value => { 120 | console.log(logged = value); 121 | }); 122 | router({req:{url:"https://www.somesite.com/1/joe"},res:{}}); 123 | expect(logged!==undefined).equal(true); 124 | }) 125 | it("object matching",function() { 126 | const objects = [ 127 | {name:"joe",age:21,address:{city:"Seattle",zipcode:"98101"}}, 128 | {name:"mary",age:20,address:{city:"Seattle",zipcode:"90101"}}, 129 | {name:"joan",age:22,address:{city:"Bainbridge Island",zipcode:"98110"}}], 130 | matches = switchcase(objects).match({age:(value) => value >=21,address:{city:"Seattle"}}); 131 | expect(Array.isArray(matches)).equal(true); 132 | expect(matches.length).equal(1); 133 | expect(matches[0]).equal(objects[0]); 134 | }); 135 | }); -------------------------------------------------------------------------------- /test/issue3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 20 | 21 | 22 | --------------------------------------------------------------------------------