├── .gitignore ├── .gitattributes ├── img └── logo.png ├── jest.config.js ├── .travis.yml ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md ├── src └── index.ts └── test └── index.test.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .npmrc 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.config.js linguist-detectable=false 2 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thedaviddelta/scope-extensions-js/HEAD/img/logo.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | globals: { 4 | "ts-jest": { 5 | tsConfig: { 6 | target: "ES6", 7 | strict: false, 8 | esModuleInterop: true 9 | } 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - lts/* 5 | 6 | before_script: 7 | - npm run build 8 | 9 | script: 10 | - npm test 11 | 12 | deploy: 13 | - provider: npm 14 | email: $NPM_EMAIL 15 | api_token: $NPM_TOKEN 16 | skip_cleanup: true 17 | on: 18 | tags: true 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "declaration": true, 5 | "rootDir": "./src", 6 | "outDir": "./dist", 7 | "strict": true 8 | }, 9 | "include": [ 10 | "src/**/*.ts" 11 | ], 12 | "exclude": [ 13 | "node_modules", 14 | "test/**/*.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 TheDavidDelta 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. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scope-extensions-js", 3 | "version": "1.1.0", 4 | "description": "Package for using Kotlin's Scope Function Extensions on JavaScript and TypeScript", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "jest" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/TheDavidDelta/scope-extensions-js.git" 14 | }, 15 | "keywords": [ 16 | "kotlin", 17 | "scope", 18 | "functions", 19 | "extensions", 20 | "scoping", 21 | "let", 22 | "also", 23 | "run", 24 | "apply", 25 | "with" 26 | ], 27 | "author": "TheDavidDelta", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/TheDavidDelta/scope-extensions-js/issues" 31 | }, 32 | "homepage": "https://github.com/TheDavidDelta/scope-extensions-js#readme", 33 | "devDependencies": { 34 | "typescript": "^3.8.3", 35 | "jest": "^23.6.0", 36 | "@types/jest": "^23.3.14", 37 | "ts-jest": "^23.10.5", 38 | "braces": ">=2.3.1" 39 | }, 40 | "files": [ 41 | "dist/**/*", 42 | "img/logo.png" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scope-extensions-js 2 | 3 | 4 | 5 | [![Build Status](https://travis-ci.com/TheDavidDelta/scope-extensions-js.svg?branch=master)](https://travis-ci.com/TheDavidDelta/scope-extensions-js) 6 | [![NPM Version](https://img.shields.io/npm/v/scope-extensions-js)](https://www.npmjs.com/package/scope-extensions-js) 7 | [![License](https://img.shields.io/github/license/TheDavidDelta/scope-extensions-js)](./LICENSE) 8 | 9 | Package for using [Kotlin's Scope Function Extensions](https://kotlinlang.org/docs/reference/scope-functions.html) on JavaScript and TypeScript. 10 | 11 | It also supports the use of the new [Optional Chaining Operator](https://github.com/tc39/proposal-optional-chaining), bringing the logic of [Kotlin's Null Safe Calls](https://kotlinlang.org/docs/reference/null-safety.html) to the JavaScript world. 12 | 13 | ## Installation 14 | 15 | Just install the package using NPM 16 | 17 | ```shell 18 | npm i --save scope-extensions-js 19 | ``` 20 | 21 | and import it directly to any file. 22 | 23 | ```javascript 24 | require("scope-extensions-js"); 25 | ``` 26 | 27 | You can also use ES6 syntax. 28 | 29 | ```javascript 30 | import "scope-extensions-js"; 31 | ``` 32 | 33 | For browser, reference directly to `node_modules` path 34 | 35 | ```html 36 | 37 | ``` 38 | 39 | or use it without installation by CDNs (`unpkg`/`jsdelivr`). 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | ```html 46 | 47 | ``` 48 | 49 | Note that the `type="module"` tag is not needed. 50 | 51 | ## Usage 52 | 53 | Simply call any value with `let`, `also`, `run` or `apply` and it'll be passed as the argument or the context of a scope function. 54 | 55 | ```typescript 56 | const obj = { name: "Daniel", age: 30 }; 57 | 58 | obj.let(it => { 59 | return it.age < 0 ? it.age : 0; 60 | }).also(it => { 61 | console.log(it); 62 | }); // prints 30 63 | ``` 64 | 65 | This way, you can execute a block of code only if a value is neither null nor undefined. 66 | 67 | ```typescript 68 | const str: string | null = await getData(); 69 | 70 | // later 71 | str?.also(it => { 72 | console.log(`Already initialized: ${it}`); 73 | }) ?? console.log("Still not initialized"); 74 | ``` 75 | 76 | The above code is equivalent to this 77 | 78 | ```typescript 79 | if (str != null && str != undefined) 80 | console.log(`Already initialized: ${str!}`); 81 | else 82 | console.log("Still not initialized"); 83 | ``` 84 | 85 | The usage of `takeIf` & `takeUnless` is a bit different. You can call any value with `takeIf` and it will return the caller instance if the predicate is `true`, or `undefined` if it's `false` (and vice versa when using `takeUnless`). 86 | 87 | ```typescript 88 | const account = await getCurrent(); 89 | 90 | account.takeIf(it => { 91 | return list.includes(it.id); 92 | })?.also(it => { 93 | console.log(it); 94 | }) ?? console.log("Not included"); 95 | ``` 96 | 97 | ## Differences 98 | 99 | We could group the 4 main extensions into 2 groups of 2 each, based on both the argument type and the return value: 100 | + `let` & `also` receive the caller instance as a function parameter, and `run` & `apply` receive the caller instance as the function context (`this`). 101 | + `let` & `run` return the function result (`return`) value, and `also` & `apply` return the caller instance (`this`). 102 | 103 | Summed up in this table: 104 | 105 | | | **`it` argument** | **`this` context** | 106 | |--------------------|:-----------------:|:------------------:| 107 | | **Returns result** | `let` | `run` | 108 | | **Returns `this`** | `also` | `apply` | 109 | 110 | 111 | Note that `let` & `also` can be called with standard lambda/arrow functions, but because JavaScript arrow functions don't have an own `this` context, `run` & `apply` have to be called with standard functions. 112 | 113 | Here is an example of each one of them: 114 | + `let` 115 | ```typescript 116 | const data: Array | null = await idsFromFile(); 117 | 118 | const str = data?.let(it => 119 | processToString(it); 120 | ) ?? "empty"; 121 | ``` 122 | + `also` 123 | ```typescript 124 | const list: Array = model.getNames(); 125 | 126 | const filtered = list.also(it => 127 | it.slice(0, 4); 128 | ).also(it => 129 | applyFilter(filter, it); 130 | ).also(console.log); 131 | 132 | // same as 133 | const filtered = list.also(it => { 134 | it.slice(0, 4); 135 | applyFilter(filter, it); 136 | console.log(it); 137 | }); 138 | ``` 139 | + `run` 140 | ```typescript 141 | const list: Array | undefined = currentAcc?.getContacts(); 142 | 143 | const lastsByName = list?.run(function() { 144 | this.filter(); 145 | this.reverse(); 146 | return this.slice(0, 3); 147 | }); 148 | ``` 149 | + `apply` 150 | ```typescript 151 | const obj = { name: "Daniel", age: 30 }; 152 | 153 | obj.apply(function() { 154 | this.name = "Dan"; 155 | this.age++; 156 | this["country"] = "Canada"; 157 | }); 158 | ``` 159 | 160 | ## License 161 | 162 | Copyright © 2020 [TheDavidDelta](https://github.com/TheDavidDelta). 163 | This project is [MIT](./LICENSE) licensed. 164 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | interface Object { 5 | /** 6 | * Calls the specified function block with `this` value as its argument and returns its result 7 | * @param block - The function to be executed with `this` as argument 8 | * @returns `block`'s result 9 | */ 10 | let(this: T | null | undefined, block: (it: T) => R): R; 11 | /** 12 | * Calls the specified function block with `this` value as its argument and returns `this` value 13 | * @param block - The function to be executed with `this` as argument 14 | * @returns `this` 15 | */ 16 | also(this: T | null | undefined, block: (it: T) => void): T; 17 | /** 18 | * Calls the specified function block with `this` value as its receiver and returns its value 19 | * @param block - The function to be executed with `this` as context 20 | * @returns `block`'s result 21 | */ 22 | run(this: T | null | undefined, block: (this: T) => R): R; 23 | /** 24 | * Calls the specified function block with `this` value as its receiver and returns `this` value 25 | * @param block - The function to be executed with `this` as context 26 | * @returns `this` 27 | */ 28 | apply(this: T | null | undefined, block: (this: T) => void): T; 29 | /** 30 | * Returns `this` value if it satisfies the given predicate or `undefined` if it doesn't 31 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 32 | * @returns `this` or `undefined` 33 | */ 34 | takeIf(this: T | null | undefined, predicate: (it: T) => boolean): T | undefined; 35 | /** 36 | * Returns `this` value if it does not satisfy the given predicate or `undefined` if it does 37 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 38 | * @returns `this` or `undefined` 39 | */ 40 | takeUnless(this: T | null | undefined, predicate: (it: T) => boolean): T | undefined; 41 | } 42 | interface Number { 43 | /** 44 | * Calls the specified function block with `this` value as its argument and returns its result 45 | * @param block - The function to be executed with `this` as argument 46 | * @returns `block`'s result 47 | */ 48 | let(this: Number | null | undefined, block: (it: number) => R): R; 49 | /** 50 | * Calls the specified function block with `this` value as its argument and returns `this` value 51 | * @param block - The function to be executed with `this` as argument 52 | * @returns `this` 53 | */ 54 | also(this: Number | null | undefined, block: (it: number) => void): number; 55 | /** 56 | * Calls the specified function block with `this` value as its receiver and returns its value 57 | * @param block - The function to be executed with `this` as context 58 | * @returns `block`'s result 59 | */ 60 | run(this: Number | null | undefined, block: (this: number) => R): R; 61 | /** 62 | * Calls the specified function block with `this` value as its receiver and returns `this` value 63 | * @param block - The function to be executed with `this` as context 64 | * @returns `this` 65 | */ 66 | apply(this: Number | null | undefined, block: (this: number) => void): number; 67 | /** 68 | * Returns `this` value if it satisfies the given predicate or `undefined` if it doesn't 69 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 70 | * @returns `this` or `undefined` 71 | */ 72 | takeIf(this: Number | null | undefined, predicate: (it: number) => boolean): number | undefined; 73 | /** 74 | * Returns `this` value if it does not satisfy the given predicate or `undefined` if it does 75 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 76 | * @returns `this` or `undefined` 77 | */ 78 | takeUnless(this: Number | null | undefined, predicate: (it: number) => boolean): number | undefined; 79 | } 80 | interface String { 81 | /** 82 | * Calls the specified function block with `this` value as its argument and returns its result 83 | * @param block - The function to be executed with `this` as argument 84 | * @returns `block`'s result 85 | */ 86 | let(this: String | null | undefined, block: (it: string) => R): R; 87 | /** 88 | * Calls the specified function block with `this` value as its argument and returns `this` value 89 | * @param block - The function to be executed with `this` as argument 90 | * @returns `this` 91 | */ 92 | also(this: String | null | undefined, block: (it: string) => void): string; 93 | /** 94 | * Calls the specified function block with `this` value as its receiver and returns its value 95 | * @param block - The function to be executed with `this` as context 96 | * @returns `block`'s result 97 | */ 98 | run(this: String | null | undefined, block: (this: string) => R): R; 99 | /** 100 | * Calls the specified function block with `this` value as its receiver and returns `this` value 101 | * @param block - The function to be executed with `this` as context 102 | * @returns `this` 103 | */ 104 | apply(this: String | null | undefined, block: (this: string) => void): string; 105 | /** 106 | * Returns `this` value if it satisfies the given predicate or `undefined` if it doesn't 107 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 108 | * @returns `this` or `undefined` 109 | */ 110 | takeIf(this: String | null | undefined, predicate: (it: string) => boolean): string | undefined; 111 | /** 112 | * Returns `this` value if it does not satisfy the given predicate or `undefined` if it does 113 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 114 | * @returns `this` or `undefined` 115 | */ 116 | takeUnless(this: String | null | undefined, predicate: (it: string) => boolean): string | undefined; 117 | } 118 | interface Boolean { 119 | /** 120 | * Calls the specified function block with `this` value as its argument and returns its result 121 | * @param block - The function to be executed with `this` as argument 122 | * @returns `block`'s result 123 | */ 124 | let(this: Boolean | null | undefined, block: (it: boolean) => R): R; 125 | /** 126 | * Calls the specified function block with `this` value as its argument and returns `this` value 127 | * @param block - The function to be executed with `this` as argument 128 | * @returns `this` 129 | */ 130 | also(this: Boolean | null | undefined, block: (it: boolean) => void): boolean; 131 | /** 132 | * Calls the specified function block with `this` value as its receiver and returns its value 133 | * @param block - The function to be executed with `this` as context 134 | * @returns `block`'s result 135 | */ 136 | run(this: Boolean | null | undefined, block: (this: boolean) => R): R; 137 | /** 138 | * Calls the specified function block with `this` value as its receiver and returns `this` value 139 | * @param block - The function to be executed with `this` as context 140 | * @returns `this` 141 | */ 142 | apply(this: Boolean | null | undefined, block: (this: boolean) => void): boolean; 143 | /** 144 | * Returns `this` value if it satisfies the given predicate or `undefined` if it doesn't 145 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 146 | * @returns `this` or `undefined` 147 | */ 148 | takeIf(this: Boolean | null | undefined, predicate?: (it: boolean) => boolean): boolean | undefined; 149 | /** 150 | * Returns `this` value if it does not satisfy the given predicate or `undefined` if it does 151 | * @param predicate - The function to be executed with `this` as argument and returns a truthy or falsy value 152 | * @returns `this` or `undefined` 153 | */ 154 | takeUnless(this: Boolean | null | undefined, predicate?: (it: boolean) => boolean): boolean | undefined; 155 | } 156 | } 157 | 158 | Object.prototype.let = function(this, block) { 159 | return block(this!); 160 | } 161 | 162 | Object.prototype.also = function(this, block) { 163 | block(this!); 164 | return this!; 165 | } 166 | 167 | Object.prototype.run = function(this, block) { 168 | return block.call(this!); 169 | } 170 | 171 | Object.prototype.apply = function(this, block) { 172 | block.call(this!); 173 | return this!; 174 | } 175 | 176 | Object.prototype.takeIf = function(this, predicate) { 177 | return predicate(this!) ? this! : undefined; 178 | } 179 | 180 | Object.prototype.takeUnless = function(this, predicate) { 181 | return predicate(this!) ? undefined : this!; 182 | } 183 | 184 | Number.prototype.let = function(this, block) { 185 | return block(this!.valueOf()); 186 | } 187 | 188 | Number.prototype.also = function(this, block) { 189 | block(this!.valueOf()); 190 | return this!.valueOf(); 191 | } 192 | 193 | Number.prototype.run = function(this, block) { 194 | return block.call(this!.valueOf()); 195 | } 196 | 197 | Number.prototype.apply = function(this, block) { 198 | block.call(this!.valueOf()); 199 | return this!.valueOf(); 200 | } 201 | 202 | Number.prototype.takeIf = function(this, predicate) { 203 | return predicate(this!.valueOf()) ? this!.valueOf() : undefined; 204 | } 205 | 206 | Number.prototype.takeUnless = function(this, predicate) { 207 | return predicate(this!.valueOf()) ? undefined : this!.valueOf(); 208 | } 209 | 210 | String.prototype.let = function(this, block) { 211 | return block(this!.valueOf()); 212 | } 213 | 214 | String.prototype.also = function(this, block) { 215 | block(this!.valueOf()); 216 | return this!.valueOf(); 217 | } 218 | 219 | String.prototype.run = function(this, block) { 220 | return block.call(this!.valueOf()); 221 | } 222 | 223 | String.prototype.apply = function(this, block) { 224 | block.call(this!.valueOf()); 225 | return this!.valueOf(); 226 | } 227 | 228 | String.prototype.takeIf = function(this, predicate) { 229 | return predicate(this!.valueOf()) ? this!.valueOf() : undefined; 230 | } 231 | 232 | String.prototype.takeUnless = function(this, predicate) { 233 | return predicate(this!.valueOf()) ? undefined : this!.valueOf(); 234 | } 235 | 236 | Boolean.prototype.let = function(this, block) { 237 | return block(this!.valueOf()); 238 | } 239 | 240 | Boolean.prototype.also = function(this, block) { 241 | block(this!.valueOf()); 242 | return this!.valueOf(); 243 | } 244 | 245 | Boolean.prototype.run = function(this, block) { 246 | return block.call(this!.valueOf()); 247 | } 248 | 249 | Boolean.prototype.apply = function(this, block) { 250 | block.call(this!.valueOf()); 251 | return this!.valueOf(); 252 | } 253 | 254 | Boolean.prototype.takeIf = function(this, predicate) { 255 | return predicate && predicate(this!.valueOf()) || this!.valueOf() ? this!.valueOf() : undefined; 256 | } 257 | 258 | Boolean.prototype.takeUnless = function(this, predicate) { 259 | return predicate && predicate(this!.valueOf()) || this!.valueOf() ? undefined : this!.valueOf(); 260 | } 261 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import "../src/index"; 2 | 3 | describe("let", () => { 4 | test("works with object", () => { 5 | const obj = { name: "Daniel", age: 30 }; 6 | obj.let(it => expect(it).toBeInstanceOf(Object)); 7 | }); 8 | test("works with number", () => { 9 | const number = 5; 10 | number.let(it => expect(typeof it).toBe("number")); 11 | number.let(it => expect(it).toBe(number)); 12 | }); 13 | test("works with string", () => { 14 | const string = "Hello world"; 15 | string.let(it => expect(typeof it).toBe("string")); 16 | string.let(it => expect(it).toBe(string)); 17 | }); 18 | test("works with boolean", () => { 19 | const boolean = true; 20 | boolean.let(it => expect(typeof it).toBe("boolean")); 21 | boolean.let(it => expect(it).toBe(boolean)); 22 | }); 23 | test("returns value", () => { 24 | const obj = { name: "Daniel", age: 30 }; 25 | const value = obj.let(it => it.age); 26 | expect(value).toBe(30); 27 | }); 28 | test("works with nullable", () => { 29 | const str: string | null = "Hello world"; 30 | const value = str?.let(it => it.split(" ")[0]); 31 | expect(value).toBe("Hello"); 32 | }); 33 | test("fails with null", () => { 34 | const str: string | null = null; 35 | const value = str?.let(it => it.split(" ")[0]); 36 | expect(value).toBeUndefined(); 37 | }); 38 | test("fails with undefined", () => { 39 | const str: string | undefined = undefined; 40 | const value = str?.let(it => it.split(" ")[0]); 41 | expect(value).toBeUndefined(); 42 | }); 43 | }); 44 | 45 | describe("also", () => { 46 | test("works with object", () => { 47 | const obj = { name: "Daniel", age: 30 }; 48 | obj.also(it => expect(it).toBeInstanceOf(Object)); 49 | }); 50 | test("works with number", () => { 51 | const number = 5; 52 | number.also(it => expect(typeof it).toBe("number")); 53 | number.also(it => expect(it).toBe(number)); 54 | }); 55 | test("works with string", () => { 56 | const string = "Hello world"; 57 | string.also(it => expect(typeof it).toBe("string")); 58 | string.also(it => expect(it).toBe(string)); 59 | }); 60 | test("works with boolean", () => { 61 | const boolean = true; 62 | boolean.also(it => expect(typeof it).toBe("boolean")); 63 | boolean.also(it => expect(it).toBe(boolean)); 64 | }); 65 | test("returns instance", () => { 66 | const obj = { name: "Daniel", age: 30 }; 67 | const value = obj.also(it => it.name); 68 | expect(value).toBe(obj); 69 | }); 70 | test("modifies value", () => { 71 | const obj = { name: "Daniel", age: 30 }; 72 | const value = obj.also(it => it.age = 40); 73 | expect(value).toBe(obj); 74 | expect(value.age).toBe(40); 75 | }); 76 | test("works with nullable", () => { 77 | const obj: object | null = { name: "Daniel", age: 30 }; 78 | const value = obj?.also(it => it["age"] = 40); 79 | expect(value).toBe(obj); 80 | expect(value?.["age"]).toBe(40); 81 | }); 82 | test("fails with null", () => { 83 | const obj: object | null = null; 84 | const value = obj?.also(it => it["age"] = 40); 85 | expect(value).toBeUndefined(); 86 | }); 87 | test("fails with undefined", () => { 88 | const obj: object | undefined = undefined; 89 | const value = obj?.also(it => it["age"] = 40); 90 | expect(value).toBeUndefined(); 91 | }); 92 | test("retains with null", () => { 93 | const str: string | null = null; 94 | const obj = { name: "Daniel", age: 30 }; 95 | str?.also(it => obj.name = it); 96 | expect(obj.name).toBe("Daniel"); 97 | }); 98 | }); 99 | 100 | describe("run", () => { 101 | test("works with object", () => { 102 | const obj = { name: "Daniel", age: 30 }; 103 | obj.run(function() { 104 | expect(this).toBeInstanceOf(Object); 105 | }); 106 | }); 107 | test("works with number", () => { 108 | const number = 5; 109 | number.run(function() { 110 | expect(typeof this).toBe("number"); 111 | }); 112 | number.run(function() { 113 | expect(this).toBe(number); 114 | }); 115 | }); 116 | test("works with string", () => { 117 | const string = "Hello world"; 118 | string.run(function() { 119 | expect(typeof this).toBe("string"); 120 | }); 121 | string.run(function() { 122 | expect(this).toBe(string); 123 | }); 124 | }); 125 | test("works with boolean", () => { 126 | const boolean = true; 127 | boolean.run(function() { 128 | expect(typeof this).toBe("boolean"); 129 | }); 130 | boolean.run(function() { 131 | expect(this).toBe(boolean); 132 | }); 133 | }); 134 | test("returns value", () => { 135 | const obj = { name: "Daniel", age: 30 }; 136 | const value = obj.run(function() { 137 | return this.age; 138 | }); 139 | expect(value).toBe(30); 140 | }); 141 | test("works with nullable", () => { 142 | const str: string | null = "Hello world"; 143 | const value = str?.run(function() { 144 | return this.split(" ")[0]; 145 | }); 146 | expect(value).toBe("Hello"); 147 | }); 148 | test("fails with null", () => { 149 | const str: string | null = null; 150 | const value = str?.run(function() { 151 | return this.split(" ")[0]; 152 | }); 153 | expect(value).toBeUndefined(); 154 | }); 155 | test("fails with undefined", () => { 156 | const str: string | undefined = undefined; 157 | const value = str?.run(function() { 158 | return this.split(" ")[0]; 159 | }); 160 | expect(value).toBeUndefined(); 161 | }); 162 | }); 163 | 164 | describe("apply", () => { 165 | test("works with object", () => { 166 | const obj = { name: "Daniel", age: 30 }; 167 | obj.apply(function() { 168 | expect(this).toBeInstanceOf(Object); 169 | }); 170 | }); 171 | test("works with number", () => { 172 | const number = 5; 173 | number.apply(function() { 174 | expect(typeof this).toBe("number"); 175 | }); 176 | number.apply(function() { 177 | expect(this).toBe(number); 178 | }); 179 | }); 180 | test("works with string", () => { 181 | const string = "Hello world"; 182 | string.apply(function() { 183 | expect(typeof this).toBe("string"); 184 | }); 185 | string.apply(function() { 186 | expect(this).toBe(string); 187 | }); 188 | }); 189 | test("works with boolean", () => { 190 | const boolean = true; 191 | boolean.apply(function() { 192 | expect(typeof this).toBe("boolean"); 193 | }); 194 | boolean.apply(function() { 195 | expect(this).toBe(boolean); 196 | }); 197 | }); 198 | test("returns instance", () => { 199 | const obj = { name: "Daniel", age: 30 }; 200 | const value = obj.apply(function() { 201 | return this.name; 202 | }); 203 | expect(value).toBe(obj); 204 | }); 205 | test("modifies value", () => { 206 | const obj = { name: "Daniel", age: 30 }; 207 | const value = obj.apply(function() { 208 | this.age = 40; 209 | }); 210 | expect(value).toBe(obj); 211 | expect(value.age).toBe(40); 212 | }); 213 | test("works with nullable", () => { 214 | const obj: object | null = { name: "Daniel", age: 30 }; 215 | const value = obj?.apply(function() { 216 | this["age"] = 40; 217 | }); 218 | expect(value).toBe(obj); 219 | expect(value?.["age"]).toBe(40); 220 | }); 221 | test("fails with null", () => { 222 | const obj: object | null = null; 223 | const value = obj?.apply(function() { 224 | this["age"] = 40; 225 | }); 226 | expect(value).toBeUndefined(); 227 | }); 228 | test("fails with undefined", () => { 229 | const obj: object | null = null; 230 | const value = obj?.apply(function() { 231 | this["age"] = 40; 232 | }); 233 | expect(value).toBeUndefined(); 234 | }); 235 | test("retains with null", () => { 236 | const str: string | null = null; 237 | const obj = { name: "Daniel", age: 30 }; 238 | str?.apply(function() { 239 | obj.name = this; 240 | }); 241 | expect(obj.name).toBe("Daniel"); 242 | }); 243 | }); 244 | 245 | describe("takeIf", () => { 246 | test("works with object", () => { 247 | const obj = { name: "Daniel", age: 30 }; 248 | obj.takeIf(it => !!expect(it).toBeInstanceOf(Object)); 249 | }); 250 | test("works with number", () => { 251 | const number = 5; 252 | number.takeIf(it => !!expect(typeof it).toBe("number")); 253 | number.takeIf(it => !!expect(it).toBe(number)); 254 | }); 255 | test("works with string", () => { 256 | const string = "Hello world"; 257 | string.takeIf(it => !!expect(typeof it).toBe("string")); 258 | string.takeIf(it => !!expect(it).toBe(string)); 259 | }); 260 | test("works with boolean", () => { 261 | const boolean = true; 262 | boolean.takeIf(it => !!expect(typeof it).toBe("boolean")); 263 | boolean.takeIf(it => expect(it).toBe(boolean)); 264 | }); 265 | test("returns instance if true", () => { 266 | const obj = { name: "Daniel", age: 30 }; 267 | const value = obj.takeIf(it => it.age < 40); 268 | expect(value).toBe(obj); 269 | }); 270 | test("returns undefined if false", () => { 271 | const obj = { name: "Daniel", age: 30 }; 272 | const value = obj.takeIf(it => it.age > 40); 273 | expect(value).toBeUndefined(); 274 | }); 275 | test("true without predicate", () => { 276 | const boolean = true; 277 | const value = boolean.takeIf(); 278 | expect(value).toBe(boolean); 279 | expect(value).toBe(true); 280 | }); 281 | test("false without predicate", () => { 282 | const boolean = false; 283 | const value = boolean.takeIf(); 284 | expect(value).toBeUndefined(); 285 | }); 286 | test("modifies value", () => { 287 | const obj = { name: "Daniel", age: 30 }; 288 | const value = obj.takeIf(it => { 289 | it.name = "George"; 290 | return it.age < 40; 291 | }); 292 | expect(value).toBe(obj); 293 | expect(value.name).toBe("George"); 294 | }); 295 | test("works with nullable", () => { 296 | const obj: object | null = { name: "Daniel", age: 30 }; 297 | const value = obj?.takeIf(it => it["age"] < 40); 298 | expect(value).toBe(obj); 299 | }); 300 | test("fails with null", () => { 301 | const obj: object | null = null; 302 | const value = obj?.takeIf(it => it["age"] < 40); 303 | expect(value).toBeUndefined(); 304 | }); 305 | test("fails with undefined", () => { 306 | const obj: object | undefined = undefined; 307 | const value = obj?.takeIf(it => it["age"] < 40); 308 | expect(value).toBeUndefined(); 309 | }); 310 | }); 311 | 312 | describe("takeUnless", () => { 313 | test("works with object", () => { 314 | const obj = { name: "Daniel", age: 30 }; 315 | obj.takeUnless(it => !!expect(it).toBeInstanceOf(Object)); 316 | }); 317 | test("works with number", () => { 318 | const number = 5; 319 | number.takeUnless(it => !!expect(typeof it).toBe("number")); 320 | }); 321 | test("works with number", () => { 322 | const number = 5; 323 | number.takeUnless(it => !!expect(typeof it).toBe("number")); 324 | number.takeUnless(it => !!expect(it).toBe(number)); 325 | }); 326 | test("works with string", () => { 327 | const string = "Hello world"; 328 | string.takeUnless(it => !!expect(typeof it).toBe("string")); 329 | string.takeUnless(it => !!expect(it).toBe(string)); 330 | }); 331 | test("works with boolean", () => { 332 | const boolean = true; 333 | boolean.takeUnless(it => !!expect(typeof it).toBe("boolean")); 334 | boolean.takeUnless(it => expect(it).toBe(boolean)); 335 | }); 336 | test("returns undefined if true", () => { 337 | const obj = { name: "Daniel", age: 30 }; 338 | const value = obj.takeUnless(it => it.age < 40); 339 | expect(value).toBeUndefined(); 340 | }); 341 | test("returns instance if false", () => { 342 | const obj = { name: "Daniel", age: 30 }; 343 | const value = obj.takeUnless(it => it.age > 40); 344 | expect(value).toBe(obj); 345 | }); 346 | test("true without predicate", () => { 347 | const boolean = true; 348 | const value = boolean.takeUnless(); 349 | expect(value).toBeUndefined(); 350 | }); 351 | test("false without predicate", () => { 352 | const boolean = false; 353 | const value = boolean.takeUnless(); 354 | expect(value).toBe(boolean); 355 | expect(value).toBe(false); 356 | }); 357 | test("modifies value", () => { 358 | const obj = { name: "Daniel", age: 30 }; 359 | const value = obj.takeUnless(it => { 360 | it.name = "George"; 361 | return it.age < 40; 362 | }); 363 | expect(value).toBeUndefined(); 364 | expect(obj.name).toBe("George"); 365 | }); 366 | test("works with nullable", () => { 367 | const obj: object | null = { name: "Daniel", age: 30 }; 368 | const value = obj?.takeUnless(it => it["age"] > 40); 369 | expect(value).toBe(obj); 370 | }); 371 | test("fails with null", () => { 372 | const obj: object | null = null; 373 | const value = obj?.takeUnless(it => it["age"] > 40); 374 | expect(value).toBeUndefined(); 375 | }); 376 | test("fails with undefined", () => { 377 | const obj: object | undefined = undefined; 378 | const value = obj?.takeUnless(it => it["age"] > 40); 379 | expect(value).toBeUndefined(); 380 | }); 381 | }); 382 | --------------------------------------------------------------------------------