├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── index.ts ├── jest.config.js ├── package.json ├── test └── spec.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | charset = utf-8 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "jest": true, 6 | "node": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "sourceType": "module", 11 | "project": "./tsconfig.json" 12 | }, 13 | "extends": [ 14 | "standard" 15 | ], 16 | "plugins": [ 17 | "@typescript-eslint" 18 | ], 19 | "rules": { 20 | "arrow-parens": "off", 21 | "camelcase": "off", 22 | "dot-notation": "off", 23 | "indent": "off", 24 | "no-unused-vars": "off", 25 | "no-useless-constructor": "off", 26 | "padded-blocks": [ 27 | "error", { 28 | "blocks": "never", 29 | "classes": "always", 30 | "switches": "never" 31 | } 32 | ], 33 | "space-before-function-paren": ["error", "always"], 34 | "@typescript-eslint/camelcase": "error", 35 | "@typescript-eslint/indent": ["error", 2], 36 | "@typescript-eslint/no-unused-vars": "error", 37 | "@typescript-eslint/no-useless-constructor": "error" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .eslintcache 4 | node_modules 5 | typings 6 | *.js 7 | *.js.map 8 | *.d.ts 9 | !jest.config.js 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .editorconfig 4 | .eslintcache 5 | .eslintrc.json 6 | .gitignore 7 | jest.config.js 8 | node_modules 9 | test 10 | tsconfig.json 11 | yarn.lock 12 | *.js.map 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 shogogg 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ts-option 2 | Scala like `Option` type for TypeScript/JavaScript. 3 | 4 | 5 | ### Install 6 | ``` 7 | # yarn 8 | $ yarn add ts-option 9 | 10 | # npm 11 | $ npm install --save ts-option 12 | ``` 13 | 14 | 15 | ### Usage 16 | #### TypeScript 17 | ``` 18 | import {Option, option, some, none} from "ts-option"; 19 | 20 | let a: Option = option(1); // Some(1) 21 | let c: Option = some(2); // Some(2) 22 | let b: Option = option(null); // None 23 | let d: Option = none; // None 24 | ``` 25 | 26 | #### JavaScript (ES Modules) 27 | ``` 28 | import {Option, option, some, none} from "ts-option"; 29 | 30 | let a = option(1); // Some(1) 31 | let c = some(2); // Some(2) 32 | let b = option(null); // None 33 | let d = none; // None 34 | ``` 35 | 36 | 37 | ### API 38 | #### `option(value?: A | null): Option` 39 | Create an `Option` instance from a value. 40 | It returns `Some` when the value is not null/undefined, otherwise returns `None`. 41 | 42 | #### `some(value: A): Option` 43 | Create an `Some` instance from a value. 44 | It returns `Some` even if the value is null or undefined. 45 | If strict null checks are enabled in your tsconfig.json, undefined and null won't be permitted here. 46 | 47 | #### `none: Option` 48 | The `None` type singleton object. 49 | 50 | #### `option(...).exists(p: (_: A) => boolean): boolean` 51 | Returns true if the option is non-empty and the predicate p returns true when applied to the option's value. 52 | 53 | #### `option(...).filter(p: (_: A) => boolean): Option` 54 | Returns the option if it is non-empty and applying the predicate p to the option's value returns true. 55 | 56 | #### `option(...).filterNot(p: (_: A) => boolean): Option` 57 | Returns the option if it is non-empty and applying the predicate p to the option's value returns false. 58 | 59 | #### `option(...).flatMap(f: (_: A) => Option): Option` 60 | Returns the result of applying f to the option's value if the option is non-empty, otherwise returns `None`. 61 | 62 | #### `option(...).fold(ifEmpty: () => B)(f: (_: A) => B): B` 63 | Returns the result of applying f to the option's value if the option is non-empty, otherwise returns `ifEmpty` value. 64 | 65 | #### `option(...).forAll(p: (_: A) => boolean): boolean` 66 | Tests whether a predicate holds for all elements of the option. 67 | 68 | #### `option(...).forComprehension(...fns: (a: any) => Option): Option` 69 | Performs a for-comprehension *like* operation using the given list of functions. For example: 70 | 71 | ``` 72 | const nestedOptions = some({ 73 | anOption: some({ 74 | anotherOption: some({ 75 | finalValue: true 76 | }) 77 | }) 78 | }); 79 | 80 | const result = nestedOptions.forComprehension( 81 | obj => obj.anOption, 82 | anOption => anOption.anotherOption, 83 | anotherOption => anotherOption.finalValue 84 | ); 85 | 86 | console.log(`${result}`) // Some(true) 87 | ``` 88 | 89 | As with the Scala for comprehension the result of each function is flat-mapped with the next, except for the last, which is mapped. 90 | 91 | Please note that there are currently some limitations: 92 | 93 | 1) Filtering must be done manually 94 | 2) There is no shared scope between functions 95 | 3) The result type is always `Option` 96 | 97 | #### `option(...).forEach(f: (_: A) => any): void` 98 | Apply the given procedure f to the option's value if it is non-empty, otherwise do nothing. 99 | 100 | #### `option(...).get: A` 101 | Returns the option's value if the option is non-empty, otherwise throws an error. 102 | 103 | #### `option(...).getOrElse(defaultValue: () => A): A` 104 | Returns the option's value if the option is non-empty, otherwise return the result of evaluating defaultValue. 105 | 106 | #### `option(...).getOrElseValue(defaultValue: A): A` 107 | Returns the option's value if the option is non-empty, otherwise return the defaultValue. 108 | 109 | #### `option(...).isDefined: boolean` 110 | Returns true if the option's value is non-empty, false otherwise. 111 | 112 | #### `option(...).isEmpty: boolean` 113 | Returns true if the option is an instance of `None`, false otherwise. 114 | 115 | #### `option(...).map(f: (_: A) => B): Option` 116 | Builds a new option by applying a function to all elements of this option. 117 | 118 | #### `option(...).match(matcher: { some: (_: A) => B, none: () => B }): B` 119 | Scala's "Pattern Match" like signature. Returns the value that the function `some` returns if the option is non-empty, 120 | otherwise returns the value that function `none` returns. 121 | 122 | ``` 123 | let s: Option = some(9); 124 | let n: Option = none; 125 | 126 | let a: number = s.match({ 127 | some: x => x * 9, 128 | none: () => -1, 129 | }); 130 | let b: number = n.match({ 131 | some: x => x * 9, 132 | none: () => -1, 133 | }); 134 | 135 | console.log(a); // 81 136 | console.log(b); // -1 137 | ``` 138 | 139 | #### `option(...).nonEmpty: boolean` 140 | Returns true if the option is an instance of `Some`, false otherwise. 141 | 142 | #### `option(...).orElse(alternative: () => Option): Option` 143 | Returns the option itself if it is non-empty, otherwise return the result of evaluating alternative. 144 | 145 | #### `option(...).orElseValue(alternative: Option): Option` 146 | Returns the option itself if it is non-empty, otherwise return the alternative. 147 | 148 | #### `option(...).orNull: A | null` 149 | Returns the option's value if it is non-empty, or null if it is empty. 150 | 151 | #### `option(...).orUndefined: A | undefined` 152 | Returns the option's value if it is non-empty, or undefined if it is empty. 153 | 154 | #### `option(...).toArray: Array` 155 | Converts the option to an array. 156 | 157 | ### License 158 | MIT 159 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 shogogg 3 | * 4 | * This software is released under the MIA License. 5 | * http://opensource.org/licenses/mit-license.php 6 | */ 7 | export interface Matcher { 8 | some: (_: A) => B 9 | none: () => B 10 | } 11 | 12 | export interface OptionLike { 13 | /** 14 | * Returns true if the option is non-empty and the predicate p returns true when applied to the option's value. 15 | */ 16 | exists (p: (_: A) => boolean): boolean 17 | 18 | /** 19 | * Returns the option if it is non-empty and applying the predicate p to the option's value returns true. 20 | */ 21 | filter (p: (_: A) => boolean): Option 22 | 23 | /** 24 | * Returns the option if it is non-empty and applying the predicate p to the option's value returns false. 25 | */ 26 | filterNot (p: (_: A) => boolean): Option 27 | 28 | /** 29 | * Returns the result of applying f to the option's value if the option is non-empty. 30 | */ 31 | flatMap (f: (_: A) => Option): Option 32 | 33 | /** 34 | * Returns the result of applying f to the option's value if the option is non-empty. 35 | * Otherwise, evaluates expression ifEmpty. 36 | */ 37 | fold (ifEmpty: () => B): (f: (_: A) => B) => B 38 | 39 | /** 40 | * Tests whether a predicate holds for all elements of the option. 41 | */ 42 | forAll (p: (_: A) => boolean): boolean 43 | 44 | /** 45 | * Performs a for-comprehension like flatMap and map operation using the given functions 46 | */ 47 | forComprehension (...fns: ((x: any) => Option)[]): Option 48 | 49 | /** 50 | * Apply the given procedure f to the option's value, if it is non-empty. 51 | */ 52 | forEach (f: (_: A) => void): void 53 | 54 | /** 55 | * Returns the option's value if the option is non-empty, otherwise throws an error. 56 | */ 57 | readonly get: A 58 | 59 | /** 60 | * Returns the option's value if the option is non-empty, otherwise return the result of evaluating defaultValue. 61 | */ 62 | getOrElse (defaultValue: () => A): A 63 | 64 | /** 65 | * Returns the option's value if the option is non-empty, otherwise return defaultValue. 66 | */ 67 | getOrElseValue (defaultValue: A): A 68 | 69 | /** 70 | * Returns true if the option's value is non-empty, false otherwise. 71 | */ 72 | readonly isDefined: boolean 73 | 74 | /** 75 | * Returns true if the option's value is empty, false otherwise. 76 | */ 77 | readonly isEmpty: boolean 78 | 79 | /** 80 | * Builds a new option by applying a function to all elements of this option. 81 | */ 82 | map (f: (_: A) => B): Option 83 | 84 | /** 85 | * Pattern match signature. 86 | */ 87 | match (matcher: Matcher): B 88 | 89 | /** 90 | * Returns true if the option's value is non-empty, false otherwise. 91 | */ 92 | readonly nonEmpty: boolean 93 | 94 | /** 95 | * Returns the option itself if it is non-empty, otherwise return the result of evaluating alternative. 96 | */ 97 | orElse (alternative: () => Option): Option 98 | 99 | /** 100 | * Returns the option itself if it is non-empty, otherwise return the alternative. 101 | */ 102 | orElseValue (alternative: Option): Option 103 | 104 | /** 105 | * Returns the option's value if it is non-empty, or null if it is empty. 106 | */ 107 | readonly orNull: A | null 108 | 109 | /** 110 | * Returns the option's value if it is non-empty, or undefined if it is empty. 111 | */ 112 | readonly orUndefined: A | undefined 113 | 114 | /** 115 | * Converts the option to an array. 116 | */ 117 | readonly toArray: A[] 118 | 119 | /** 120 | * Returns a string representation 121 | */ 122 | toString (): string 123 | 124 | } 125 | 126 | export abstract class Option implements OptionLike { 127 | abstract exists (p: (_: A) => boolean): boolean 128 | 129 | abstract filter (p: (_: A) => boolean): Option 130 | 131 | abstract filterNot (p: (_: A) => boolean): Option 132 | 133 | abstract flatMap (f: (_: A) => Option): Option 134 | 135 | abstract fold (ifEmpty: () => B): (f: (_: A) => B) => B 136 | 137 | abstract forAll (p: (_: A) => boolean): boolean 138 | 139 | abstract forComprehension (...fns: ((x: any) => Option)[]): Option 140 | 141 | abstract forEach (f: (_: A) => void): void 142 | 143 | readonly get: A 144 | 145 | abstract getOrElse (defaultValue: () => A): A 146 | 147 | abstract getOrElseValue (defaultValue: A): A 148 | 149 | readonly isDefined: boolean 150 | readonly isEmpty: boolean 151 | 152 | abstract map (f: (_: A) => B): Option 153 | 154 | abstract match (matcher: Matcher): B 155 | 156 | readonly nonEmpty: boolean 157 | 158 | abstract orElse (alternative: () => Option): Option 159 | 160 | abstract orElseValue (alternative: Option): Option 161 | 162 | readonly orNull: A | null 163 | readonly orUndefined: A | undefined 164 | readonly toArray: A[] 165 | 166 | abstract toString (): string 167 | } 168 | 169 | export class Some extends Option implements OptionLike { 170 | constructor (private readonly value: A) { 171 | super() 172 | } 173 | 174 | exists (p: (_: A) => boolean): boolean { 175 | return p(this.value) 176 | } 177 | 178 | filter (p: (_: A) => boolean): Option { 179 | return p(this.value) ? this : none 180 | } 181 | 182 | filterNot (p: (_: A) => boolean): Option { 183 | return p(this.value) ? none : this 184 | } 185 | 186 | flatMap (f: (_: A) => Option): Option { 187 | return f(this.value) 188 | } 189 | 190 | fold (/* ifEmpty: () => B) */): (f: (_: A) => B) => B { 191 | return f => f(this.value) 192 | } 193 | 194 | forAll (p: (_: A) => boolean): boolean { 195 | return p(this.value) 196 | } 197 | 198 | forComprehension (...fns: ((x: any) => Option)[]): Option { 199 | let result: Option = this 200 | for (let i = 0; i < fns.length - 1; ++i) { 201 | result = result.flatMap(fns[i]) 202 | } 203 | return result.map(fns[fns.length - 1]) 204 | } 205 | 206 | forEach (f: (_: A) => void): void { 207 | return f(this.value) 208 | } 209 | 210 | get get (): A { 211 | return this.value 212 | } 213 | 214 | getOrElse (/* defaultValue: () => A) */): A { 215 | return this.value 216 | } 217 | 218 | getOrElseValue (/* defaultValue: A */): A { 219 | return this.value 220 | } 221 | 222 | get isDefined (): boolean { 223 | return true 224 | } 225 | 226 | get isEmpty (): boolean { 227 | return false 228 | } 229 | 230 | map (f: (_: A) => B): Option { 231 | return some(f(this.value)) 232 | } 233 | 234 | match (matcher: Matcher): B { 235 | return matcher.some(this.value) 236 | } 237 | 238 | get nonEmpty (): boolean { 239 | return true 240 | } 241 | 242 | orElse (/* alternative: () => Option */): Option { 243 | return this 244 | } 245 | 246 | orElseValue (/* alternative: Option */): Option { 247 | return this 248 | } 249 | 250 | get orNull (): A | null { 251 | return this.value 252 | } 253 | 254 | get orUndefined (): A | undefined { 255 | return this.value 256 | } 257 | 258 | get toArray (): A[] { 259 | return [this.value] 260 | } 261 | 262 | toString (): string { 263 | return 'Some(' + this.value + ')' 264 | } 265 | } 266 | 267 | export class None extends Option implements OptionLike { 268 | exists (/* p: (_: A) => boolean */): boolean { 269 | return false 270 | } 271 | 272 | filter (/* p: (_: A) => boolean */): Option { 273 | return this 274 | } 275 | 276 | filterNot (/* p: (_: A) => boolean */): Option { 277 | return this 278 | } 279 | 280 | flatMap (/* f: (_: A) => Option */): Option { 281 | return none 282 | } 283 | 284 | fold (ifEmpty: () => B): (f: (_: A) => B) => B { 285 | return () => ifEmpty() 286 | } 287 | 288 | forAll (/* p: (_: A) => boolean */): boolean { 289 | return true 290 | } 291 | 292 | forComprehension (/* ...fns: ((x: any) => Option)[] */): Option { 293 | return none 294 | } 295 | 296 | forEach (): void { 297 | // do nothing. 298 | } 299 | 300 | get get (): A { 301 | throw new Error('No such element.') 302 | } 303 | 304 | getOrElse (defaultValue: () => A): A { 305 | return defaultValue() 306 | } 307 | 308 | getOrElseValue (defaultValue: A): A { 309 | return defaultValue 310 | } 311 | 312 | get isDefined (): boolean { 313 | return false 314 | } 315 | 316 | get isEmpty (): boolean { 317 | return true 318 | } 319 | 320 | map (/* f: (_: A) => B */): Option { 321 | return none 322 | } 323 | 324 | match (matcher: Matcher): B { 325 | return matcher.none() 326 | } 327 | 328 | get nonEmpty (): boolean { 329 | return false 330 | } 331 | 332 | orElse (alternative: () => Option): Option { 333 | return alternative() 334 | } 335 | 336 | orElseValue (alternative: Option): Option { 337 | return alternative 338 | } 339 | 340 | get orNull (): A | null { 341 | return null 342 | } 343 | 344 | get orUndefined (): A | undefined { 345 | return undefined 346 | } 347 | 348 | get toArray (): Array { 349 | return [] 350 | } 351 | 352 | toString (): string { 353 | return 'None' 354 | } 355 | } 356 | 357 | export function some (value: A): Option { 358 | return new Some(value) 359 | } 360 | 361 | export const none: Option = new None() 362 | 363 | export function option (value?: A | null): Option { 364 | return value === null || typeof value === 'undefined' ? none : some(value) 365 | } 366 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest' 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-option", 3 | "version": "2.1.0", 4 | "description": "Scala like Option type for TypeScript", 5 | "author": "shogogg ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "typings": "index.d.ts", 9 | "keywords": [ 10 | "Option", 11 | "Optional", 12 | "Maybe", 13 | "TypeScript" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/shogogg/ts-option.git" 18 | }, 19 | "scripts": { 20 | "build": "tsc", 21 | "clean": "rimraf *.js *.d.ts", 22 | "clean:all": "rimraf *.js *.d.ts node_modules", 23 | "lint": "eslint --cache --ext .ts --ignore-path .gitignore .", 24 | "test": "jest", 25 | "test:watch": "jest --watch" 26 | }, 27 | "devDependencies": { 28 | "@types/jest": "^24.0.11", 29 | "@typescript-eslint/eslint-plugin": "^1.7.0", 30 | "eslint": "^5.16.0", 31 | "eslint-config-import": "^0.13.0", 32 | "eslint-config-standard": "^12.0.0", 33 | "eslint-plugin-import": "^2.17.2", 34 | "eslint-plugin-node": "^8.0.1", 35 | "eslint-plugin-promise": "^4.1.1", 36 | "eslint-plugin-standard": "^4.0.0", 37 | "espower-typescript": "^9.0.0", 38 | "jest": "^24.7.1", 39 | "rimraf": "^2.5.3", 40 | "ts-jest": "^24.0.2", 41 | "typescript": "^3.4.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 shogogg 3 | * 4 | * This software is released under the MIT License. 5 | * http://opensource.org/licenses/mit-license.php 6 | */ 7 | import { none, None, option, Option, some, Some } from '../index' 8 | 9 | describe('option', () => { 10 | it('should be a function', () => { 11 | expect(typeof option).toBe('function') 12 | }) 13 | 14 | it('should return `none` when null given', () => { 15 | expect(option(null)).toBe(none) 16 | }) 17 | 18 | it('should return `none` when undefined given', () => { 19 | expect(option(undefined)).toBe(none) 20 | }) 21 | 22 | it('should return `Some` instance when non-empty value given', () => { 23 | expect(option(1)).toBeInstanceOf(Some) 24 | expect(option('foo')).toBeInstanceOf(Some) 25 | expect(option(new Date())).toBeInstanceOf(Some) 26 | }) 27 | }) 28 | 29 | describe('some', () => { 30 | it('should be a function', () => { 31 | expect(typeof some).toBe('function') 32 | }) 33 | 34 | it('should return `Some` instance', () => { 35 | expect(some(null)).toBeInstanceOf(Some) 36 | expect(some(undefined)).toBeInstanceOf(Some) 37 | expect(some(1)).toBeInstanceOf(Some) 38 | expect(some('bar')).toBeInstanceOf(Some) 39 | expect(some(new Date())).toBeInstanceOf(Some) 40 | }) 41 | }) 42 | 43 | describe('none', () => { 44 | it('should be a `None` instance', () => { 45 | expect(none).toBeInstanceOf(None) 46 | }) 47 | }) 48 | 49 | describe('Some', () => { 50 | it('should be instance of `Option`', () => { 51 | expect(some('foo')).toBeInstanceOf(Option) 52 | }) 53 | 54 | describe('#exists', () => { 55 | it('should return true when the predicate return true', () => { 56 | expect(some(1).exists(a => a === 1)).toBe(true) 57 | }) 58 | 59 | it('should return false when the predicate return false', () => { 60 | expect(some(1).exists(a => a !== 1)).toBe(false) 61 | }) 62 | }) 63 | 64 | describe('#filter', () => { 65 | const x = some('foo') 66 | 67 | it('should return itself when the predicate return true', () => { 68 | expect(x.filter(a => a === 'foo')).toBe(x) 69 | }) 70 | 71 | it('should return `None` when the predicate return false', () => { 72 | expect(x.filter(a => a === 'bar')).toBe(none) 73 | }) 74 | }) 75 | 76 | describe('#filterNot', () => { 77 | const x = some('foo') 78 | 79 | it('should return `None` when the predicate return true', () => { 80 | expect(x.filterNot(a => a === 'foo')).toBe(none) 81 | }) 82 | 83 | it('should return itself when the predicate return false', () => { 84 | expect(x.filterNot(a => a === 'bar')).toBe(x) 85 | }) 86 | }) 87 | 88 | describe('#flatMap', () => { 89 | it('should return the value that given function return', () => { 90 | const f = (x: number): Option => x === 2 ? some('foo') : none 91 | expect(some(2).flatMap(f).get).toBe('foo') 92 | expect(some(1).flatMap(f)).toBe(none) 93 | }) 94 | }) 95 | 96 | describe('#fold', () => { 97 | it('should return the value that given function return', () => { 98 | expect(some(2).fold(() => -1)(x => x * 3)).toBe(6) 99 | expect(some(5).fold(() => -1)(x => x * 4)).toBe(20) 100 | }) 101 | }) 102 | 103 | describe('#forAll', () => { 104 | it('should return true when the predicate return true', () => { 105 | expect(some(3).forAll(x => x === 3)).toBe(true) 106 | expect(some(8).forAll(x => x % 2 === 0)).toBe(true) 107 | }) 108 | 109 | it('should return false when the predicate return false', () => { 110 | expect(some(2).forAll(x => x === 3)).toBe(false) 111 | expect(some(7).forAll(x => x % 2 === 0)).toBe(false) 112 | }) 113 | }) 114 | 115 | describe('#forEach', () => { 116 | it('should call the procedure', () => { 117 | const spy = jest.fn() 118 | some('foo').forEach(spy) 119 | expect(spy).toHaveBeenCalledTimes(1) 120 | expect(spy).toHaveBeenCalledWith('foo') 121 | }) 122 | }) 123 | 124 | describe('#get', () => { 125 | it('should return the option\'s value', () => { 126 | expect(some('bar').get).toBe('bar') 127 | }) 128 | }) 129 | 130 | describe('#getOrElse', () => { 131 | it('should return the option\'s value', () => { 132 | expect(some(123).getOrElse(() => 456)).toBe(123) 133 | }) 134 | }) 135 | 136 | describe('#getOrElseValue', () => { 137 | it('should return the option\'s value', () => { 138 | expect(some(123).getOrElseValue(456)).toBe(123) 139 | }) 140 | }) 141 | 142 | describe('#isDefined', () => { 143 | it('should be true', () => { 144 | expect(some(new Date()).isDefined).toBe(true) 145 | }) 146 | }) 147 | 148 | describe('#isEmpty', () => { 149 | it('should be false', () => { 150 | expect(some('typescript').isEmpty).toBe(false) 151 | }) 152 | }) 153 | 154 | describe('#map', () => { 155 | it('should return new `Some` instance with the value that the function return', () => { 156 | const stub = jest.fn().mockReturnValueOnce(517) 157 | expect(some(2008).map(stub).get).toBe(517) 158 | expect(stub).toHaveBeenCalledTimes(1) 159 | expect(stub).toHaveBeenCalledWith(2008) 160 | }) 161 | }) 162 | 163 | describe('#match', () => { 164 | it('should return the value that function `some` return', () => { 165 | const ret = some(2).match({ 166 | some: x => x * 2, 167 | none: () => 0 168 | }) 169 | expect(ret).toBe(4) 170 | }) 171 | 172 | it('should NOT call the `none` function', () => { 173 | const spy = jest.fn() 174 | some('foo').match({ 175 | some: x => x.length, 176 | none: spy 177 | }) 178 | expect(spy).not.toHaveBeenCalled() 179 | }) 180 | }) 181 | 182 | describe('#nonEmpty', () => { 183 | it('should be true', () => { 184 | expect(some('option').nonEmpty).toBe(true) 185 | }) 186 | }) 187 | 188 | describe('#orElse', () => { 189 | it('should return itself', () => { 190 | const x = some('foo') 191 | expect(x.orElse(() => some('bar'))).toBe(x) 192 | }) 193 | }) 194 | 195 | describe('#orElseValue', () => { 196 | it('should return itself', () => { 197 | const x = some('foo') 198 | expect(x.orElseValue(some('bar'))).toBe(x) 199 | }) 200 | }) 201 | 202 | describe('#orNull', () => { 203 | it('should return the option\'s value', () => { 204 | expect(some(2016).orNull).toBe(2016) 205 | }) 206 | }) 207 | 208 | describe('#orUndefined', () => { 209 | it('should return the option\'s value', () => { 210 | expect(some(2019).orUndefined).toBe(2019) 211 | }) 212 | }) 213 | 214 | describe('#toArray', () => { 215 | it('should return an array of option\'s value', () => { 216 | expect(some('js').toArray).toStrictEqual(['js']) 217 | }) 218 | }) 219 | 220 | describe('#forComprehension', () => { 221 | it('should flat map every method except the last, which is mapped', () => { 222 | const nestedOptions = some({ 223 | anOption: some({ 224 | anotherOption: some({ 225 | finalValue: true 226 | }) 227 | }) 228 | }) 229 | const result = nestedOptions.forComprehension( 230 | obj => obj.anOption, 231 | anOption => anOption.anotherOption, 232 | anotherOption => anotherOption.finalValue 233 | ) 234 | expect(result.get === true) 235 | }) 236 | }) 237 | 238 | describe('#toString', () => { 239 | it('should return the \'Some(2016)\' string when the value === 2016', () => { 240 | expect(some(2016).toString() === 'Some(2016)') 241 | }) 242 | 243 | it('should return the \'Some(false)\' string when the value === false', () => { 244 | expect(some(false).toString() === 'Some(false)') 245 | }) 246 | 247 | it('should return the \'Some(toto)\' string when the value === \'toto\'', () => { 248 | expect(some('toto').toString() === 'Some(toto)') 249 | }) 250 | }) 251 | }) 252 | 253 | describe('None', () => { 254 | it('should be instance of `Option`', () => { 255 | expect(none).toBeInstanceOf(Option) 256 | }) 257 | 258 | describe('#exists', () => { 259 | it('should return false', () => { 260 | expect(none.exists(a => a === 1)).toBe(false) 261 | expect(none.exists(a => a !== 1)).toBe(false) 262 | }) 263 | }) 264 | 265 | describe('#filter', () => { 266 | it('should return `none`', () => { 267 | expect(none.filter(() => true) === none) 268 | }) 269 | 270 | it('should NOT call the predicate', () => { 271 | const stub = jest.fn().mockReturnValue(true) 272 | none.filter(stub) 273 | expect(stub).not.toHaveBeenCalled() 274 | }) 275 | }) 276 | 277 | describe('#filterNot', () => { 278 | it('should return `none`', () => { 279 | expect(none.filter(() => false) === none) 280 | }) 281 | 282 | it('should NOT call the predicate', () => { 283 | const stub = jest.fn().mockReturnValue(false) 284 | none.filterNot(stub) 285 | expect(stub).not.toHaveBeenCalled() 286 | }) 287 | }) 288 | 289 | describe('#flatMap', () => { 290 | it('should return `none`', () => { 291 | expect(none.flatMap(() => some(1)) === none) 292 | }) 293 | 294 | it('should NOT call the function', () => { 295 | const stub = jest.fn().mockReturnValue(some(1)) 296 | none.flatMap(stub) 297 | expect(stub).not.toHaveBeenCalled() 298 | }) 299 | }) 300 | 301 | describe('#fold', () => { 302 | it('should return `ifEmpty`', () => { 303 | const stub = jest.fn().mockReturnValue('foo') 304 | expect(none.fold(stub)(() => 'bar') === 'foo') 305 | expect(stub).toHaveBeenCalledTimes(1) 306 | }) 307 | 308 | it('should NOT call the function', () => { 309 | const stub = jest.fn().mockReturnValue('bar') 310 | none.fold(() => 'foo')(stub) 311 | expect(stub).not.toHaveBeenCalled() 312 | }) 313 | }) 314 | 315 | describe('#forAll', () => { 316 | it('should return true', () => { 317 | expect(none.forAll(() => false)).toBe(true) 318 | }) 319 | 320 | it('should NOT call the predicate', () => { 321 | const stub = jest.fn().mockReturnValue(true) 322 | none.forAll(stub) 323 | expect(stub).not.toHaveBeenCalled() 324 | }) 325 | }) 326 | 327 | describe('#forEach', () => { 328 | it('should NOT call the procedure', () => { 329 | const stub = jest.fn() 330 | none.forEach(stub) 331 | expect(stub).not.toHaveBeenCalled() 332 | }) 333 | }) 334 | 335 | describe('#get', () => { 336 | it('should throw an error', () => { 337 | expect(() => none.get).toThrow('No such element') 338 | }) 339 | }) 340 | 341 | describe('#getOrElse', () => { 342 | it('should return the result of `defaultValue`', () => { 343 | const option: Option = none 344 | expect(option.getOrElse(() => 123)).toBe(123) 345 | }) 346 | }) 347 | 348 | describe('#getOrElseValue', () => { 349 | it('should return `defaultValue`', () => { 350 | const option: Option = none 351 | expect(option.getOrElseValue(123)).toBe(123) 352 | }) 353 | }) 354 | 355 | describe('#isDefined', () => { 356 | it('should be false', () => { 357 | expect(none.isDefined).toBe(false) 358 | }) 359 | }) 360 | 361 | describe('#isEmpty', () => { 362 | it('should be true', () => { 363 | expect(none.isEmpty).toBe(true) 364 | }) 365 | }) 366 | 367 | describe('#map', () => { 368 | it('should return `none`', () => { 369 | expect(none.map(() => 1) === none) 370 | }) 371 | 372 | it('should NOT call the function', () => { 373 | const stub = jest.fn().mockReturnValue(1234) 374 | none.map(stub) 375 | expect(stub).not.toHaveBeenCalled() 376 | }) 377 | }) 378 | 379 | describe('#match', () => { 380 | it('should return the value that function `none` return', () => { 381 | const option: Option = none 382 | const ret = option.match({ 383 | some: x => x * 2, 384 | none: () => 1234 385 | }) 386 | expect(ret === 1234) 387 | }) 388 | 389 | it('should NOT call the `none` function', () => { 390 | const spy = jest.fn() 391 | none.match({ 392 | some: spy, 393 | none: () => 'MOMOIRO CLOVER Z' 394 | }) 395 | expect(spy).not.toHaveBeenCalled() 396 | }) 397 | }) 398 | 399 | describe('#nonEmpty', () => { 400 | it('should be false', () => { 401 | expect(none.nonEmpty).toBe(false) 402 | }) 403 | }) 404 | 405 | describe('#orElse', () => { 406 | it('should return the result of `alternative`', () => { 407 | const option: Option = none 408 | const alternative = some('foo') 409 | expect(option.orElse(() => alternative)).toBe(alternative) 410 | }) 411 | }) 412 | 413 | describe('#orElseValue', () => { 414 | it('should return `alternative` value', () => { 415 | const option: Option = none 416 | const alternative = some('foo') 417 | expect(option.orElseValue(alternative)).toBe(alternative) 418 | }) 419 | }) 420 | 421 | describe('#orNull', () => { 422 | it('should return null', () => { 423 | expect(none.orNull).toBeNull() 424 | }) 425 | }) 426 | 427 | describe('#orUndefined', () => { 428 | it('should return undefined', () => { 429 | expect(none.orUndefined).toBeUndefined() 430 | }) 431 | }) 432 | 433 | describe('#toArray', () => { 434 | it('should return an empty array', () => { 435 | expect(none.toArray).toStrictEqual([]) 436 | }) 437 | }) 438 | 439 | describe('#forComprehension', () => { 440 | it('should return none', () => { 441 | const nestedOptions = some({ 442 | anOption: some({ 443 | anotherOption: none 444 | }) 445 | }) 446 | const result = nestedOptions.forComprehension( 447 | obj => obj.anOption, 448 | anOption => anOption.anotherOption, 449 | anotherOption => anotherOption.finalValue 450 | ) 451 | expect(result).toBe(none) 452 | }) 453 | }) 454 | 455 | describe('#toString', () => { 456 | it('should return \'None\'', () => { 457 | expect(none.toString()).toBe('None') 458 | }) 459 | }) 460 | }) 461 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es2015"], 5 | "module": "commonjs", 6 | "declaration": true, 7 | "noImplicitAny": true, 8 | "removeComments": false, 9 | "moduleResolution": "node", 10 | "sourceMap": false, 11 | "strict": true, 12 | "strictPropertyInitialization": false 13 | } 14 | } 15 | --------------------------------------------------------------------------------