├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── _config.yml ├── benchmarks ├── filter-find.ts ├── filter-map-slice.ts ├── filter-map.ts ├── map-filter-sum.ts ├── map-slice.ts └── map-sum.ts ├── examples ├── fibonacci.ts ├── filtering.ts ├── joins.ts └── sets.ts ├── gulpfile.ts ├── images └── map-filter-slice.PNG ├── index.ts ├── lib ├── index.ts ├── iterators │ ├── concat.ts │ ├── distinct.ts │ ├── exclude.ts │ ├── fill.ts │ ├── filter.ts │ ├── flat.ts │ ├── groupJoin.ts │ ├── intersect.ts │ ├── join.ts │ ├── leftJoin.ts │ ├── map.ts │ ├── reverse.ts │ ├── shuffle.ts │ ├── skip.ts │ ├── skipWhile.ts │ ├── slice.ts │ ├── splice.ts │ ├── take.ts │ └── takeWhile.ts ├── reducers │ ├── average.ts │ ├── first.ts │ ├── indexOf.ts │ ├── last.ts │ ├── lastIndexOf.ts │ ├── length.ts │ ├── max.ts │ ├── min.ts │ ├── nth.ts │ ├── reduce.ts │ ├── sum.ts │ ├── toArray.ts │ ├── toGroups.ts │ ├── toMap.ts │ └── toSet.ts ├── types │ ├── IterableCast.ts │ ├── IterableFilter.ts │ ├── IterableJoin.ts │ ├── IterablePermutation.ts │ ├── IterablePredicate.ts │ ├── IterableQuery.ts │ ├── IterableSet.ts │ ├── IterableTransformation.ts │ └── IterableValue.ts └── utils │ ├── isIterable.ts │ ├── iterable.ts │ └── iterator.ts ├── package.json ├── test ├── helpers │ ├── SpyIterable.ts │ └── generators.ts ├── iterators │ ├── concat.test.ts │ ├── distinct.test.ts │ ├── exclude.test.ts │ ├── fill.test.ts │ ├── filter.test.ts │ ├── flat.test.ts │ ├── groupJoin.test.ts │ ├── intersect.test.ts │ ├── join.test.ts │ ├── leftJoin.test.ts │ ├── map.test.ts │ ├── reverse.test.ts │ ├── shuffle.test.ts │ ├── skip.test.ts │ ├── skipWhile.test.ts │ ├── slice.test.ts │ ├── splice.test.ts │ ├── take.test.ts │ └── takeWhile.test.ts ├── itiriri.test.ts ├── itiriri │ ├── cast.test.ts │ ├── filter.test.ts │ ├── itiriri.test.ts │ ├── join.test.ts │ ├── permutation.test.ts │ ├── predicate.test.ts │ ├── set.test.ts │ ├── transformation.test.ts │ └── value.test.ts ├── reducers │ ├── average.test.ts │ ├── first.test.ts │ ├── indexOf.test.ts │ ├── last.test.ts │ ├── lastIndexOf.test.ts │ ├── length.test.ts │ ├── max.test.ts │ ├── min.test.ts │ ├── nth.test.ts │ ├── reduce.tests.ts │ ├── sum.test.ts │ ├── toArray.test.ts │ ├── toMap.test.ts │ ├── toPartitions.test.ts │ └── toSet.test.ts └── utils │ ├── isIterable.test.ts │ └── iterator.test.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | package-lock.json 5 | itiriri.min.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "lts/*" 4 | - "node" 5 | cache: 6 | directories: 7 | - "node_modules" 8 | install: 9 | - "npm install" 10 | - "gulp tslint" 11 | - "gulp test" 12 | script: "npm run coverage" 13 | after_script: "npm install coveralls@3.0.0 && cat ./coverage/lcov.info | coveralls" 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${file}", 12 | "outFiles": [ 13 | "${workspaceFolder}/**/*.js" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "explorer.confirmDragAndDrop": false, 3 | "editor.tabSize": 2, 4 | "editor.renderWhitespace": "all", 5 | "editor.formatOnSave": true, 6 | "explorer.confirmDelete": false, 7 | "files.exclude": { 8 | "**/node_modules": true, 9 | "**/build": true, 10 | "**/coverage": true 11 | } 12 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.15.0-jessie 2 | 3 | WORKDIR /usr/src/app/ 4 | COPY . /usr/src/app/ 5 | 6 | RUN npm install 7 | RUN npm install -g gulp 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Labs42 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 | 23 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /benchmarks/filter-find.ts: -------------------------------------------------------------------------------- 1 | import { Suite, Event } from 'benchmark'; 2 | import { default as itiriri} from '../lib'; 3 | 4 | const input: number[] = []; 5 | 6 | for (let i = 0; i < 100000; i++) { 7 | input.push(Math.random()); 8 | } 9 | 10 | const suite = new Suite('Find index of an element within filtered result.'); 11 | 12 | suite.add('itiriri', () => { 13 | itiriri(input) 14 | .filter(x => x < 0.5) 15 | .findIndex(x => x.toString().startsWith('0.42')); 16 | }); 17 | 18 | suite.add('Array', () => { 19 | input 20 | .filter(x => x < 0.5) 21 | .findIndex(x => x.toString().startsWith('0.42')); 22 | }); 23 | 24 | suite.on('cycle', (event: Event) => { 25 | console.log(String(event.target)); 26 | }); 27 | 28 | suite.on('complete', function (this: Suite) { 29 | console.log('Fastest is ' + this.filter('fastest').map('name' as any)); 30 | }); 31 | 32 | suite.run({ async: false }); 33 | 34 | -------------------------------------------------------------------------------- /benchmarks/filter-map-slice.ts: -------------------------------------------------------------------------------- 1 | import { Suite, Event } from 'benchmark'; 2 | import { default as itiriri } from '../lib'; 3 | 4 | const sizes = [1000, 5000, 10000, 50000, 100000, 200000]; 5 | const inputs: number[][] = []; 6 | 7 | sizes.forEach((size, idx) => { 8 | inputs[idx] = []; 9 | 10 | for (let i = 0; i < size; i++) { 11 | inputs[idx].push(Math.random()); 12 | } 13 | }); 14 | 15 | const suite = new Suite(); 16 | 17 | sizes.forEach((size, idx) => { 18 | const arr = inputs[idx]; 19 | const takeCount = 100; 20 | 21 | suite.add(`itiriri ${size}`, () => { 22 | itiriri(arr) 23 | .filter(x => x < 0.5) 24 | .map(x => x * 100) 25 | .take(takeCount) 26 | .toArray(); 27 | }); 28 | 29 | suite.add(`Array ${size}`, () => { 30 | arr 31 | .filter(x => x < 0.5) 32 | .map(x => x * 100) 33 | .slice(0, takeCount); 34 | }); 35 | }); 36 | 37 | suite.on('cycle', (event: Event) => { 38 | console.log(String(event.target)); 39 | }); 40 | 41 | suite.on('complete', function (this: Suite) { 42 | console.log('Fastest is ' + this.filter('fastest').map('name' as any)); 43 | }); 44 | 45 | suite.run({ async: true }); 46 | 47 | -------------------------------------------------------------------------------- /benchmarks/filter-map.ts: -------------------------------------------------------------------------------- 1 | import { Suite, Event } from 'benchmark'; 2 | import { default as itiriri} from '../lib'; 3 | 4 | const sizes = [1000000]; 5 | const inputs: { name: string, value: number }[][] = []; 6 | 7 | sizes.forEach((size, idx) => { 8 | inputs[idx] = []; 9 | 10 | for (let i = 0; i < size; i++) { 11 | inputs[idx].push({ 12 | name: `itiriri-${i}`, 13 | value: Math.floor(Math.random() * 100), 14 | }); 15 | } 16 | }); 17 | 18 | const suite = new Suite(); 19 | 20 | sizes.forEach((size, idx) => { 21 | const arr = inputs[idx]; 22 | 23 | suite.add(`itiriri ${size}`, () => { 24 | itiriri(arr) 25 | .filter(x => x.value > 40) 26 | .filter(x => x.value < 60) 27 | .map(x => x.name.toUpperCase()) 28 | .toArray(); 29 | }); 30 | 31 | suite.add(`Array ${size}`, () => { 32 | arr 33 | .filter(x => x.value > 40) 34 | .filter(x => x.value < 60) 35 | .map(x => x.name.toUpperCase()); 36 | }); 37 | }); 38 | 39 | suite.on('cycle', (event: Event) => { 40 | console.log(String(event.target)); 41 | }); 42 | 43 | suite.on('complete', function (this: Suite) { 44 | console.log('Fastest is ' + this.filter('fastest').map('name' as any)); 45 | }); 46 | 47 | suite.run({ async: true }); 48 | 49 | -------------------------------------------------------------------------------- /benchmarks/map-filter-sum.ts: -------------------------------------------------------------------------------- 1 | import { Suite, Event } from 'benchmark'; 2 | import { default as itiriri } from '../lib'; 3 | 4 | const input: number[] = []; 5 | 6 | for (let i = 0; i < 100000; i++) { 7 | input.push(Math.random()); 8 | } 9 | 10 | const suite = new Suite(); 11 | 12 | suite.add('itiriri', () => { 13 | itiriri(input) 14 | .map(x => ({ value: x * 100 })) 15 | .filter(x => x.value < 50) 16 | .sum(x => x.value); 17 | }); 18 | 19 | suite.add('Array', () => { 20 | input 21 | .map(x => ({ value: x * 100 })) 22 | .filter(x => x.value < 50) 23 | .reduce((a, b) => a + b.value, 0); 24 | }); 25 | 26 | suite.on('cycle', (event: Event) => { 27 | console.log(String(event.target)); 28 | }); 29 | 30 | suite.on('complete', function (this: Suite) { 31 | console.log('Fastest is ' + this.filter('fastest').map('name' as any)); 32 | }); 33 | 34 | suite.run({ async: true }); 35 | 36 | -------------------------------------------------------------------------------- /benchmarks/map-slice.ts: -------------------------------------------------------------------------------- 1 | import { Suite, Event } from 'benchmark'; 2 | import { default as itiriri } from '../lib'; 3 | 4 | const input: number[] = []; 5 | 6 | for (let i = 0; i < 100000; i++) { 7 | input.push(Math.random()); 8 | } 9 | 10 | const suite = new Suite(); 11 | 12 | suite.add('itiriri', () => { 13 | itiriri(input) 14 | .map(x => x * 100) 15 | .take(1000) 16 | .toArray(); 17 | }); 18 | 19 | suite.add('Array', () => { 20 | input 21 | .map(x => x * 100) 22 | .slice(0, 1000); 23 | }); 24 | 25 | suite.on('cycle', (event: Event) => { 26 | console.log(String(event.target)); 27 | }); 28 | 29 | suite.on('complete', function (this: Suite) { 30 | console.log('Fastest is ' + this.filter('fastest').map('name' as any)); 31 | }); 32 | 33 | suite.run({ async: true }); 34 | 35 | -------------------------------------------------------------------------------- /benchmarks/map-sum.ts: -------------------------------------------------------------------------------- 1 | import { Suite, Event } from 'benchmark'; 2 | import { default as itiriri } from '../lib'; 3 | 4 | const input: number[] = []; 5 | 6 | for (let i = 0; i < 100000; i++) { 7 | input.push(Math.random()); 8 | } 9 | 10 | const suite = new Suite(); 11 | 12 | suite.add('itiriri', () => { 13 | itiriri(input) 14 | .map(x => x * 100) 15 | .sum(); 16 | }); 17 | 18 | suite.add('Array', () => { 19 | input 20 | .map(x => x * 10) 21 | .reduce((a, b) => a + b, 0); 22 | }); 23 | 24 | suite.on('cycle', (event: Event) => { 25 | console.log(String(event.target)); 26 | }); 27 | 28 | suite.on('complete', function (this: Suite) { 29 | console.log('Fastest is ' + this.filter('fastest').map('name' as any)); 30 | }); 31 | 32 | suite.run({ async: true }); 33 | 34 | -------------------------------------------------------------------------------- /examples/fibonacci.ts: -------------------------------------------------------------------------------- 1 | import { default as itiriri } from '../lib'; 2 | 3 | function* fibonacci() { 4 | let [a, b] = [0, 1]; 5 | 6 | while (true) { 7 | yield a; 8 | [a, b] = [b, a + b]; 9 | } 10 | } 11 | 12 | // Get 42nd Fibonacci number 13 | const f42 = itiriri(fibonacci()).nth(42); 14 | console.log(`Fibonacci[42]: ${f42}`); 15 | 16 | // Calculating sum of first 10 17 | const topN = (n: number) => itiriri(fibonacci()).take(n); 18 | console.log(`Top 10: ${topN(10)}`); 19 | console.log(`Sum of 10: ${topN(10).sum()}`); 20 | 21 | // Get 5 random Fibonacci numbers from first 42 22 | const random = itiriri(fibonacci()) 23 | .take(42) 24 | .shuffle() 25 | .take(5); 26 | 27 | console.log(`5 random Fibonacci numbers: ${random}`); 28 | 29 | // Finding first 5 Fibonacci numbers that contain 42 30 | const top5Has42 = itiriri(fibonacci()) 31 | .filter(x => x.toString().indexOf('42') !== -1) 32 | .take(5); 33 | 34 | console.log(`Top 5 that contain 42: ${top5Has42}`); 35 | 36 | // Group first 100 in odd/even 37 | const group = topN(100) 38 | .groupBy(x => x % 2 === 0) 39 | .map(x => `${x[0] ? 'Evens' : 'Odds'} group has ${x[1].length()} elements`); 40 | 41 | console.log(group.toString()); 42 | -------------------------------------------------------------------------------- /examples/filtering.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labs42io/itiriri/8ff1ffaa47d8940a7e821cbd80dc9c8972eb4e30/examples/filtering.ts -------------------------------------------------------------------------------- /examples/joins.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labs42io/itiriri/8ff1ffaa47d8940a7e821cbd80dc9c8972eb4e30/examples/joins.ts -------------------------------------------------------------------------------- /examples/sets.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labs42io/itiriri/8ff1ffaa47d8940a7e821cbd80dc9c8972eb4e30/examples/sets.ts -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import * as browserify from 'browserify'; 2 | import * as del from 'del'; 3 | import * as gulp from 'gulp'; 4 | import * as mocha from 'gulp-mocha'; 5 | import * as replace from 'gulp-replace'; 6 | import * as shell from 'gulp-shell'; 7 | import * as sourcemaps from 'gulp-sourcemaps'; 8 | import { default as tslint } from 'gulp-tslint'; 9 | import * as ts from 'gulp-typescript'; 10 | import { default as uglify } from 'gulp-uglify-es'; 11 | import { Gulpclass, MergedTask, SequenceTask, Task } from 'gulpclass'; 12 | import * as buffer from 'vinyl-buffer'; 13 | import * as source from 'vinyl-source-stream'; 14 | 15 | @Gulpclass() 16 | export class Gulpfile { 17 | 18 | /** 19 | * Cleans build folder. 20 | */ 21 | @Task() 22 | clean() { 23 | return del(['./build/**', './coverage/**']); 24 | } 25 | 26 | /** 27 | * Runs typescript files compilation. 28 | */ 29 | @Task() 30 | compile() { 31 | return gulp.src('./package.json', { read: false }) 32 | .pipe(shell(['tsc'])); 33 | } 34 | 35 | /** 36 | * Runs unit-tests. 37 | */ 38 | @Task() 39 | unit() { 40 | return gulp.src('./build/compiled/test/**/*.js') 41 | .pipe(mocha()); 42 | } 43 | 44 | /** 45 | * Compiles the code and runs tests. 46 | */ 47 | @SequenceTask() 48 | test() { 49 | return ['clean', 'compile', 'unit']; 50 | } 51 | 52 | /** 53 | * Runs the tslint. 54 | */ 55 | @Task() 56 | tslint() { 57 | return gulp.src(['./lib/**/*.ts', './test/**/*.ts', './examples/**/*.ts']) 58 | .pipe(tslint({ formatter: 'stylish' })) 59 | .pipe(tslint.report({ 60 | emitError: true, 61 | summarizeFailureOutput: true, 62 | })); 63 | } 64 | 65 | /** 66 | * Copies all sources to the package directory. 67 | */ 68 | @MergedTask() 69 | packageCompile() { 70 | const tsProject = ts.createProject('tsconfig.json'); 71 | const tsResult = gulp.src(['lib/**/*.ts']) 72 | .pipe(sourcemaps.init()) 73 | .pipe(tsProject()); 74 | 75 | return [ 76 | tsResult.dts.pipe(gulp.dest('build/package')), 77 | tsResult.js 78 | .pipe(sourcemaps.write('.', { sourceRoot: '', includeContent: true })) 79 | .pipe(gulp.dest('build/package')), 80 | ]; 81 | } 82 | 83 | /** 84 | * Moves all compiled files to the final package directory. 85 | */ 86 | @Task() 87 | packageMoveCompiledFiles() { 88 | return gulp.src('./build/package/lib/**/*') 89 | .pipe(gulp.dest('./build/package')); 90 | } 91 | 92 | /** 93 | * Clears the directory with compiled files. 94 | */ 95 | @Task() 96 | packageClearCompileDirectory() { 97 | return del(['build/package/lib/**']); 98 | } 99 | 100 | /** 101 | * Change the "private" state of the packaged package.json file to public. 102 | */ 103 | @Task() 104 | packagePreparePackageFile() { 105 | return gulp.src('./package.json') 106 | .pipe(replace('\"private\": true,', '\"private\": false,')) 107 | .pipe(gulp.dest('./build/package')); 108 | } 109 | 110 | /** 111 | * This task will replace all typescript code blocks in the README 112 | * (since npm does not support typescript syntax highlighting) 113 | * and copy this README file into the package folder. 114 | */ 115 | @Task() 116 | packageReadmeFile() { 117 | return gulp.src('./README.md') 118 | .pipe(replace(/```ts([\s\S]*?)```/g, '```javascript$1```')) 119 | .pipe(gulp.dest('./build/package')); 120 | } 121 | 122 | /** 123 | * Creates a package that can be published to npm. 124 | */ 125 | @SequenceTask() 126 | package() { 127 | return [ 128 | 'clean', 129 | 'packageCompile', 130 | 'packageMoveCompiledFiles', 131 | 'packageClearCompileDirectory', 132 | ['packagePreparePackageFile', 'packageReadmeFile'], 133 | ]; 134 | } 135 | 136 | /** 137 | * Publishes a package to npm from ./build/package directory. 138 | */ 139 | @Task() 140 | npmPublish() { 141 | return gulp.src('./package.json', { read: false }) 142 | .pipe(shell(['cd ./build/package && npm publish --access public'])); 143 | } 144 | 145 | /** 146 | * Creates a package and publishes it to npm. 147 | */ 148 | @SequenceTask() 149 | publish() { 150 | return ['test', 'tslint', 'package', 'npmPublish']; 151 | } 152 | 153 | @Task() 154 | bundle() { 155 | return browserify({ 156 | standalone: 'itiriri', 157 | entries: './build/compiled/lib/index.js', 158 | }).bundle() 159 | .on('error', e => console.error(e)) 160 | .pipe(source('itiriri.min.js')) 161 | .pipe(buffer()) 162 | .pipe(uglify()) 163 | .pipe(gulp.dest('.')); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /images/map-filter-slice.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labs42io/itiriri/8ff1ffaa47d8940a7e821cbd80dc9c8972eb4e30/images/map-filter-slice.PNG -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import itiriri from './lib'; 2 | 3 | // This is a sample test file. 4 | 5 | const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 6 | const q = itiriri(arr); 7 | 8 | console.log(`Length: ${q.length()}`); 9 | console.log(`Length > 5: ${q.length(x => x > 5)}`); 10 | 11 | console.log(`Average: ${q.average()}`); 12 | console.log(`Minimum: ${q.min()}`); 13 | console.log(`Maximum: ${q.max()}`); 14 | console.log(`Sum: ${q.sum()}`); 15 | 16 | console.log(`First: ${q.first()}`); 17 | console.log(`First > 5: ${q.find(x => x > 5)}`); 18 | console.log(`Last: ${q.last()}`); 19 | console.log(`Last < 5: ${q.findLast(x => x < 5)}`); 20 | 21 | console.log(`Nth(0): ${q.nth(0)}`); 22 | console.log(`Nth(100): ${q.nth(100)}`); 23 | 24 | console.log(`Every > 0: ${q.every(x => x > 0)}`); 25 | console.log(`Every > 5: ${q.every(x => x > 5)}`); 26 | console.log(`Some > 5: ${q.some(x => x > 5)}`); 27 | console.log(`Some > 10: ${q.every(x => x > 10)}`); 28 | 29 | console.log(`Sort: ${q.sort().toArray().join(', ')}`); 30 | console.log(`Reverse: ${q.reverse().toArray().join(', ')}`); 31 | 32 | console.log(`Filter > 5: ${q.filter(x => x > 5).toArray().join(', ')}`); 33 | console.log(`Exclude [1,2,9]: ${q.exclude([1, 2, 9]).toArray().join(', ')}`); 34 | console.log(`Map: ${q.map(x => x % 2 === 0 ? 0 : 1).toArray().join(', ')}`); 35 | console.log(`MapAll: 36 | ${q.flat(x => x % 2 === 0 ? [x] : [x, 10 * x]).toArray().join(', ')}`); 37 | 38 | console.log(`Take 3: ${q.take(3).toArray().join(', ')}`); 39 | console.log(`Skip 7: ${q.skip(7).toArray().join(', ')}`); 40 | 41 | console.log(`Distinct: ${q.distinct().toArray().join(', ')}`); 42 | console.log(`DistinctBy: ${q.distinct(x => x % 2 === 0 ? 0 : 1).toArray().join(', ')}`); 43 | 44 | console.log(`ForEach: ${q.map(x => ({ x })).forEach(x => x.x *= 10)}`); 45 | console.log(`GroupBy: ${q.groupBy(x => x < 5 ? 0 : 1) 46 | .toArray() 47 | .map(x => `(${x[0]})[${x[1].toArray().join(', ')}]`).join(', ')}`); 48 | console.log(`Concat: ${q.concat(itiriri([11, 12])).toArray().join(', ')}`); 49 | console.log(`ToMap: ${q.toMap(x => x, x => x)}`); 50 | 51 | for (const e of q) { 52 | console.log(e); 53 | } 54 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import { concat } from './iterators/concat'; 2 | import { distinct } from './iterators/distinct'; 3 | import { exclude } from './iterators/exclude'; 4 | import { fill } from './iterators/fill'; 5 | import { filter } from './iterators/filter'; 6 | import { flat } from './iterators/flat'; 7 | import { groupJoin } from './iterators/groupJoin'; 8 | import { intersect } from './iterators/intersect'; 9 | import { join } from './iterators/join'; 10 | import { leftJoin } from './iterators/leftJoin'; 11 | import { map } from './iterators/map'; 12 | import { reverse } from './iterators/reverse'; 13 | import { shuffle } from './iterators/shuffle'; 14 | import { skip } from './iterators/skip'; 15 | import { skipWhile } from './iterators/skipWhile'; 16 | import { slice } from './iterators/slice'; 17 | import { splice } from './iterators/splice'; 18 | import { take } from './iterators/take'; 19 | import { takeWhile } from './iterators/takeWhile'; 20 | import { average } from './reducers/average'; 21 | import { first } from './reducers/first'; 22 | import { indexOf } from './reducers/indexOf'; 23 | import { last } from './reducers/last'; 24 | import { lastIndexOf } from './reducers/lastIndexOf'; 25 | import { length } from './reducers/length'; 26 | import { max } from './reducers/max'; 27 | import { min } from './reducers/min'; 28 | import { nth } from './reducers/nth'; 29 | import { reduce } from './reducers/reduce'; 30 | import { sum } from './reducers/sum'; 31 | import { toArray } from './reducers/toArray'; 32 | import { toGroups } from './reducers/toGroups'; 33 | import { toMap } from './reducers/toMap'; 34 | import { toSet } from './reducers/toSet'; 35 | import { IterableQuery } from './types/IterableQuery'; 36 | import { isIterable } from './utils/isIterable'; 37 | import { iterable } from './utils/iterable'; 38 | import { iterator } from './utils/iterator'; 39 | 40 | /** 41 | * Creates a queryable iterable. 42 | * @param source can be an array or any other iterable. 43 | */ 44 | export default function itiriri(source: Iterable): IterableQuery { 45 | return new Itiriri(source); 46 | } 47 | 48 | export { IterableQuery }; 49 | 50 | class Itiriri implements IterableQuery{ 51 | constructor(private readonly source: Iterable) { 52 | } 53 | 54 | [Symbol.iterator](): Iterator { 55 | return iterator(this.source); 56 | } 57 | 58 | // #region common methods 59 | entries(): IterableQuery<[number, T]> { 60 | return new Itiriri( 61 | map(this.source, (elem, idx) => <[number, T]>[idx, elem]), 62 | ); 63 | } 64 | 65 | keys(): IterableQuery { 66 | return new Itiriri(map(this.source, (_, idx) => idx)); 67 | } 68 | 69 | values(): IterableQuery { 70 | return new Itiriri(this.source); 71 | } 72 | 73 | forEach(action: (element: T, index: number) => void): void { 74 | for (const [index, value] of this.entries()) { 75 | action(value, index); 76 | } 77 | } 78 | 79 | concat(other: T | Iterable): IterableQuery { 80 | return isIterable(other) ? 81 | new Itiriri(concat(this.source, other)) : 82 | new Itiriri(concat(this.source, [other])); 83 | } 84 | 85 | prepend(other: T | Iterable): IterableQuery { 86 | return isIterable(other) ? 87 | new Itiriri(concat(other, this.source)) : 88 | new Itiriri(concat([other], this.source)); 89 | } 90 | 91 | fill(value: T, start?: number, end?: number): IterableQuery { 92 | return new Itiriri(fill(this.source, value, start, end)); 93 | } 94 | // #endregion 95 | 96 | // #region IterableValue implementation 97 | nth(index: number): T | undefined { 98 | return nth(this.source, index); 99 | } 100 | 101 | indexOf(element: T, fromIndex: number = 0): number { 102 | return indexOf(this.source, (elem, idx) => idx >= fromIndex && elem === element); 103 | } 104 | 105 | findIndex(predicate: (element: T, index: number) => boolean): number { 106 | return indexOf(this.source, predicate); 107 | } 108 | 109 | lastIndexOf(element: T, fromIndex: number = 0): number { 110 | return lastIndexOf(this.source, (elem, idx) => idx >= fromIndex && elem === element); 111 | } 112 | 113 | findLastIndex(predicate: (element: T, index: number) => boolean): number { 114 | return lastIndexOf(this.source, predicate); 115 | } 116 | 117 | length(predicate: (element: T, index: number) => boolean = alwaysTrue()): number { 118 | return length(filter(this.source, predicate)); 119 | } 120 | 121 | first(): T | undefined { 122 | return first(this.source); 123 | } 124 | 125 | find(predicate: (element: T, index: number) => boolean): T | undefined { 126 | return first(filter(this.source, predicate)); 127 | } 128 | 129 | last(): T | undefined { 130 | return last(this.source); 131 | } 132 | 133 | findLast(predicate: (element: T, index: number) => boolean): T | undefined { 134 | return last(filter(this.source, predicate)); 135 | } 136 | 137 | average(selector: (element: T, index: number) => number = element()): number | undefined { 138 | return average(map(this.source, selector)); 139 | } 140 | 141 | min(compareFn: (element1: T, element2: T) => number = comparer()): T | undefined { 142 | return min(this.source, compareFn); 143 | } 144 | 145 | max(compareFn: (element1: T, element2: T) => number = comparer()): T | undefined { 146 | return max(this.source, compareFn); 147 | } 148 | 149 | sum(selector: (element: T, index: number) => number = element()): number | undefined { 150 | return sum(map(this.source, selector)); 151 | } 152 | 153 | reduce( 154 | callback: (accumulator: TResult | T, current: T, index: number) => any, 155 | initialValue?: any, 156 | ): any { 157 | return reduce(this.source, callback, initialValue); 158 | } 159 | 160 | reduceRight( 161 | callback: (accumulator: TResult | T, current: T, index: number) => any, 162 | initialValue?: TResult, 163 | ): any { 164 | return reduce(reverse(this.source), callback, initialValue); 165 | } 166 | // #endregion 167 | 168 | // #region IterablePredicate implementation 169 | every(predicate: (element: T, index: number) => boolean): boolean { 170 | return indexOf(this.source, (e, i) => !predicate(e, i)) === -1; 171 | } 172 | 173 | some(predicate: (element: T, index: number) => boolean): boolean { 174 | return indexOf(this.source, predicate) !== -1; 175 | } 176 | 177 | includes(element: T, fromIndex: number = 0): boolean { 178 | return this.some((elem, idx) => idx >= fromIndex && elem === element); 179 | } 180 | // #endregion 181 | 182 | // #region IterablePermutation implementation 183 | sort( 184 | compareFn: (element1: T, element2: T) => number = comparer(), 185 | ): IterableQuery { 186 | const source = this.source; 187 | const sortable = iterable(function* () { 188 | yield* toArray(source).sort(compareFn); 189 | }); 190 | 191 | return new Itiriri(sortable); 192 | } 193 | 194 | shuffle(): IterableQuery { 195 | return new Itiriri(shuffle(this.source)); 196 | } 197 | 198 | reverse(): IterableQuery { 199 | return new Itiriri(reverse(this.source)); 200 | } 201 | // #endregion 202 | 203 | // #region IterableFilter implementation 204 | filter(predicate: (element: T, index: number) => boolean): IterableQuery { 205 | return new Itiriri(filter(this.source, predicate)); 206 | } 207 | 208 | take(count: number): IterableQuery { 209 | return new Itiriri(take(this.source, count)); 210 | } 211 | 212 | takeWhile(predicate: (element: T, index: number) => boolean): IterableQuery { 213 | return new Itiriri(takeWhile(this.source, predicate)); 214 | } 215 | 216 | skip(count: number): IterableQuery { 217 | return new Itiriri(skip(this.source, count)); 218 | } 219 | 220 | skipWhile(predicate: (element: T, index: number) => boolean): IterableQuery { 221 | return new Itiriri(skipWhile(this.source, predicate)); 222 | } 223 | 224 | slice(start?: number, end?: number): IterableQuery { 225 | return new Itiriri(slice(this.source, start, end)); 226 | } 227 | 228 | splice(start: number, deleteCount: number, ...items: T[]): IterableQuery { 229 | return new Itiriri(splice(this.source, start, deleteCount, items)); 230 | } 231 | // #endregion 232 | 233 | // #region IterableTransformation implementation 234 | map(selector: (element: T, index: number) => S): IterableQuery { 235 | return new Itiriri(map(this.source, selector)); 236 | } 237 | 238 | flat(selector: (element: T, index: number) => Iterable): IterableQuery { 239 | return new Itiriri(flat(map(this.source, selector))); 240 | } 241 | 242 | groupBy( 243 | keySelector: (element: T, index: number) => K, 244 | valueSelector: (element: T, index: number) => S = x => x, 245 | ): IterableQuery<[K, IterableQuery]> { 246 | const source = this.source; 247 | const groups = iterable(function* () { 248 | yield* toGroups(source, keySelector, valueSelector); 249 | }); 250 | const result = map(groups, elem => <[K, IterableQuery]>[elem[0], new Itiriri(elem[1])]); 251 | 252 | return new Itiriri(result); 253 | } 254 | 255 | // #endregion 256 | 257 | // #region IterableSet implementation 258 | distinct(selector: (element: T) => S = element()): IterableQuery { 259 | return new Itiriri(distinct(this.source, selector)); 260 | } 261 | 262 | exclude(other: Iterable, selector: (element: T) => S = element()): IterableQuery { 263 | return new Itiriri(exclude(this.source, other, selector)); 264 | } 265 | 266 | intersect(other: Iterable, selector: (element: T) => S = element()): IterableQuery { 267 | return new Itiriri(intersect(this.source, other, selector)); 268 | } 269 | 270 | union(other: Iterable, selector: (element: T) => S = element()): IterableQuery { 271 | return new Itiriri(distinct(concat(this.source, other), selector)); 272 | } 273 | // #endregion 274 | 275 | // #region IterableJoin implementation 276 | join( 277 | other: Iterable, 278 | leftKeySelector: (element: T, index: number) => TKey, 279 | rightKeySelector: (element: TRight, index: number) => TKey, 280 | joinSelector: (left: T, right: TRight) => TResult, 281 | ): IterableQuery { 282 | const iterator = join( 283 | this.source, 284 | other, 285 | leftKeySelector, 286 | rightKeySelector, 287 | joinSelector); 288 | 289 | return new Itiriri(iterator); 290 | } 291 | 292 | leftJoin( 293 | other: Iterable, 294 | leftKeySelector: (element: T, index: number) => TKey, 295 | rightKeySelector: (element: TRight, index: number) => TKey, 296 | joinSelector: (left: T, right?: TRight) => TResult, 297 | ): IterableQuery { 298 | const iterator = leftJoin( 299 | this.source, 300 | other, 301 | leftKeySelector, 302 | rightKeySelector, 303 | joinSelector); 304 | 305 | return new Itiriri(iterator); 306 | } 307 | 308 | rightJoin( 309 | other: Iterable, 310 | rightKeySelector: (element: TRight, index: number) => TKey, 311 | leftKeySelector: (element: T, index: number) => TKey, 312 | joinSelector: (right: TRight, left?: T) => TResult, 313 | ): IterableQuery { 314 | const iterator = leftJoin( 315 | other, 316 | this.source, 317 | rightKeySelector, 318 | leftKeySelector, 319 | joinSelector, 320 | ); 321 | 322 | return new Itiriri(iterator); 323 | } 324 | 325 | groupJoin( 326 | other: Iterable, 327 | leftKeySelector: (element: T, index: number) => TKey, 328 | rightKeySelector: (element: TRight, index: number) => TKey, 329 | joinSelector: (left: T, right: TRight[]) => TResult, 330 | ): IterableQuery { 331 | const iterator = groupJoin( 332 | this.source, 333 | other, 334 | leftKeySelector, 335 | rightKeySelector, 336 | joinSelector); 337 | 338 | return new Itiriri(iterator); 339 | } 340 | // #endregion 341 | 342 | // #region IterableCast implementation 343 | toArray(selector?: (element: T, index: number) => S): (T | S)[] { 344 | return selector ? toArray(map(this.source, selector)) : 345 | toArray(this.source); 346 | } 347 | 348 | toMap( 349 | keySelector: (element: T, index: number) => K = element(), 350 | valueSelector: (element: T, index: number) => E = element(), 351 | ): Map { 352 | return toMap(this.source, keySelector, valueSelector); 353 | } 354 | 355 | toGroups( 356 | keySelector: (element: T, index: number) => K = element(), 357 | valueSelector: (element: T, index: number) => E = element(), 358 | ): Map { 359 | return toGroups(this.source, keySelector, valueSelector); 360 | } 361 | 362 | toSet(selector: (element: T, index: number) => S = element()): Set { 363 | return toSet(map(this.source, selector)); 364 | } 365 | 366 | toString(): string { 367 | return toArray(this.source).toString(); 368 | } 369 | // #endregion 370 | } 371 | 372 | function element() { 373 | return (e: any) => e; 374 | } 375 | 376 | function alwaysTrue() { 377 | return () => true; 378 | } 379 | 380 | function comparer() { 381 | return (a: T, b: T) => a === b ? 0 : (a > b ? 1 : -1); 382 | } 383 | -------------------------------------------------------------------------------- /lib/iterators/concat.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function concat( 4 | left: Iterable, 5 | right: Iterable, 6 | ): Iterable { 7 | return iterable(function* () { 8 | yield* left; 9 | yield* right; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /lib/iterators/distinct.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function distinct( 4 | source: Iterable, 5 | keySelector: (element: TElement) => TKey, 6 | ): Iterable { 7 | return iterable(function* () { 8 | const set = new Set(); 9 | 10 | for (const element of source) { 11 | const key = keySelector(element); 12 | 13 | if (!set.has(key)) { 14 | set.add(key); 15 | yield element; 16 | } 17 | } 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/iterators/exclude.ts: -------------------------------------------------------------------------------- 1 | import { toSet } from '../reducers/toSet'; 2 | import { iterable } from '../utils/iterable'; 3 | import { map } from './map'; 4 | 5 | export function exclude( 6 | source: Iterable, 7 | exclude: Iterable, 8 | keySelector: (element: TElement) => TKey, 9 | ): Iterable { 10 | return iterable(function* () { 11 | const exclusionSet = toSet(map(exclude, keySelector)); 12 | 13 | for (const element of source) { 14 | const key = keySelector(element); 15 | 16 | if (!exclusionSet.has(key)) { 17 | yield element; 18 | } 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /lib/iterators/fill.ts: -------------------------------------------------------------------------------- 1 | import { map } from './map'; 2 | 3 | export function fill( 4 | source: Iterable, 5 | value: T, 6 | start?: number, 7 | end?: number, 8 | ): Iterable { 9 | if (start === undefined && end === undefined) { 10 | return map(source, () => value); 11 | } 12 | 13 | if (end === undefined) { 14 | if (start !== undefined && start < 0) { 15 | throw new Error('Invalid start range, use positive index.'); 16 | } 17 | 18 | return map( 19 | source, 20 | (elem, idx) => start !== undefined && idx >= start ? value : elem); 21 | } 22 | 23 | if (end < 0) { 24 | throw new Error('Invalid end range, use positive index.'); 25 | } 26 | 27 | return map( 28 | source, 29 | (elem, idx) => start !== undefined && idx >= start && idx < end ? value : elem); 30 | } 31 | -------------------------------------------------------------------------------- /lib/iterators/filter.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function filter( 4 | source: Iterable, 5 | predicate: (element: TElement, index: number) => boolean, 6 | ): Iterable { 7 | return iterable(function* () { 8 | let index = 0; 9 | 10 | for (const element of source) { 11 | if (predicate(element, index++)) { 12 | yield element; 13 | } 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/iterators/flat.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function flat(iterables: Iterable>): Iterable { 4 | return iterable(function* () { 5 | for (const element of iterables) { 6 | yield* element; 7 | } 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /lib/iterators/groupJoin.ts: -------------------------------------------------------------------------------- 1 | import { toGroups } from '../reducers/toGroups'; 2 | import { iterable } from '../utils/iterable'; 3 | 4 | export function groupJoin( 5 | source: Iterable, 6 | others: Iterable, 7 | leftKeySelector: (element: TLeft, index: number) => TKey, 8 | rightKeySelector: (element: TRight, index: number) => TKey, 9 | joinSelector: (left: TLeft, right: TRight[]) => TResult, 10 | ): Iterable { 11 | return iterable(function* () { 12 | let index = 0; 13 | const rightMap = toGroups(others, rightKeySelector, x => x); 14 | 15 | for (const element of source) { 16 | const leftKey = leftKeySelector(element, index++); 17 | const rightValues = rightMap.get(leftKey) || []; 18 | 19 | yield joinSelector(element, rightValues); 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/iterators/intersect.ts: -------------------------------------------------------------------------------- 1 | import { toSet } from '../reducers/toSet'; 2 | import { iterable } from '../utils/iterable'; 3 | import { map } from './map'; 4 | 5 | export function intersect( 6 | source: Iterable, 7 | others: Iterable, 8 | selector: (element: TElement) => TKey, 9 | ): Iterable { 10 | return iterable(function* () { 11 | const includedSet = new Set(); 12 | const othersSet = toSet(map(others, selector)); 13 | 14 | for (const element of source) { 15 | const key = selector(element); 16 | 17 | if (!includedSet.has(key) && othersSet.has(key)) { 18 | includedSet.add(key); 19 | yield element; 20 | } 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/iterators/join.ts: -------------------------------------------------------------------------------- 1 | import { toGroups } from '../reducers/toGroups'; 2 | import { iterable } from '../utils/iterable'; 3 | 4 | export function join( 5 | source: Iterable, 6 | others: Iterable, 7 | leftKeySelector: (element: TLeft, index: number) => TKey, 8 | rightKeySelector: (element: TRight, index: number) => TKey, 9 | joinSelector: (left: TLeft, right: TRight) => TResult, 10 | ): Iterable { 11 | return iterable(function* () { 12 | let index = 0; 13 | const rightMap = toGroups(others, rightKeySelector, x => x); 14 | 15 | for (const element of source) { 16 | const leftKey = leftKeySelector(element, index++); 17 | 18 | const rightValues = rightMap.get(leftKey); 19 | if (rightValues) { 20 | for (const rightMatch of rightValues) { 21 | yield joinSelector(element, rightMatch); 22 | } 23 | } 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /lib/iterators/leftJoin.ts: -------------------------------------------------------------------------------- 1 | import { toGroups } from '../reducers/toGroups'; 2 | import { iterable } from '../utils/iterable'; 3 | 4 | export function leftJoin( 5 | source: Iterable, 6 | others: Iterable, 7 | leftKeySelector: (element: TLeft, index: number) => TKey, 8 | rightKeySelector: (element: TRight, index: number) => TKey, 9 | joinSelector: (left: TLeft, right?: TRight) => TResult, 10 | ): Iterable { 11 | return iterable(function* () { 12 | let index = 0; 13 | const rightMap = toGroups(others, rightKeySelector, x => x); 14 | 15 | for (const element of source) { 16 | const leftKey = leftKeySelector(element, index++); 17 | 18 | const rightValues = rightMap.get(leftKey); 19 | if (rightValues) { 20 | for (const rightMatch of rightValues) { 21 | yield joinSelector(element, rightMatch); 22 | } 23 | } else { 24 | yield joinSelector(element); 25 | } 26 | 27 | } 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/iterators/map.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function map( 4 | source: Iterable, 5 | transform: (element: TElement, index: number) => TResult, 6 | ): Iterable { 7 | return iterable(function* () { 8 | let index = 0; 9 | 10 | for (const element of source) { 11 | yield transform(element, index++); 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/iterators/reverse.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | import { toArray } from '../reducers/toArray'; 3 | 4 | export function reverse(source: Iterable): Iterable { 5 | return iterable(function* () { 6 | const elements = toArray(source); 7 | 8 | for (let n = elements.length - 1, i = n; i >= 0; i--) { 9 | yield elements[i]; 10 | } 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /lib/iterators/shuffle.ts: -------------------------------------------------------------------------------- 1 | import { toArray } from '../reducers/toArray'; 2 | import { iterable } from '../utils/iterable'; 3 | 4 | export function shuffle(source: Iterable): Iterable { 5 | return iterable(function* () { 6 | const elements = toArray(source); 7 | 8 | // Fisher–Yates shuffle 9 | // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 10 | for (let i = elements.length - 1; i > 0; i--) { 11 | const j = Math.floor(Math.random() * (i + 1)); 12 | [elements[i], elements[j]] = [elements[j], elements[i]]; 13 | } 14 | 15 | yield* elements; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/iterators/skip.ts: -------------------------------------------------------------------------------- 1 | import { filter } from './filter'; 2 | import { iterable } from '../utils/iterable'; 3 | 4 | export function skip( 5 | source: Iterable, 6 | count: number, 7 | ): Iterable { 8 | return count >= 0 ? 9 | filter(source, (_, idx) => idx >= count) : 10 | iterable(() => skipLast(source, -count)); 11 | 12 | } 13 | 14 | function* skipLast( 15 | source: Iterable, 16 | count: number): IterableIterator { 17 | 18 | const buffer: TElement[] = []; 19 | let index = 0; 20 | 21 | for (const element of source) { 22 | buffer.push(element); 23 | 24 | if (index++ - count >= 0) { 25 | yield buffer.shift(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/iterators/skipWhile.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function skipWhile( 4 | source: Iterable, 5 | predicate: (element: TElement, index: number) => boolean, 6 | ): Iterable { 7 | return iterable(function* () { 8 | let index = 0; 9 | let skipped = false; 10 | 11 | for (const element of source) { 12 | if (!skipped && predicate(element, index++)) { 13 | continue; 14 | } 15 | skipped = true; 16 | yield element; 17 | } 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/iterators/slice.ts: -------------------------------------------------------------------------------- 1 | import { take } from './take'; 2 | import { skip } from './skip'; 3 | 4 | export function slice( 5 | source: Iterable, 6 | start?: number, 7 | end?: number, 8 | ): Iterable { 9 | if (start === undefined && end === undefined) { 10 | return source; 11 | } 12 | 13 | if (start !== undefined && end === undefined) { 14 | return skip(source, check(start, 'start')); 15 | } 16 | 17 | if (start === undefined && end !== undefined) { 18 | return take(source, check(end, 'end')); 19 | } 20 | 21 | // start !== undefined && end !== undefined 22 | return skip(take(source, check(end, 'end')), check(start, 'start')); 23 | } 24 | 25 | function check(value: number, name: string) { 26 | if (value < 0) { 27 | throw new Error(`Invalid ${name} range, use positive index.`); 28 | } 29 | 30 | return value; 31 | } 32 | -------------------------------------------------------------------------------- /lib/iterators/splice.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function splice( 4 | source: Iterable, 5 | start: number, 6 | deleteCount: number, 7 | items: TElement[], 8 | ): Iterable { 9 | if (start < 0) { 10 | throw new Error('Invalid start range, use positive index.'); 11 | } 12 | 13 | if (deleteCount < 0) { 14 | throw new Error('Delete count can not be negative.'); 15 | } 16 | 17 | return iterable(function* () { 18 | let index = 0; 19 | let toDelete = deleteCount; 20 | 21 | for (const element of source) { 22 | if (index++ < start) { 23 | yield element; 24 | continue; 25 | } 26 | 27 | if (toDelete-- === 0) { 28 | yield* items; 29 | } 30 | 31 | if (toDelete >= 0) { 32 | continue; 33 | } 34 | 35 | yield element; 36 | } 37 | 38 | // toDelete >= 0 when items should pe at the end 39 | if (toDelete >= 0) { 40 | yield* items; 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /lib/iterators/take.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function take( 4 | source: Iterable, 5 | count: number, 6 | ): Iterable { 7 | return count >= 0 ? 8 | iterable(() => takeFirst(source, count)) : 9 | iterable(() => takeLast(source, -count)); 10 | } 11 | 12 | function* takeFirst(source: Iterable, count: number) { 13 | let n = count; 14 | 15 | for (const element of source) { 16 | if (n-- === 0) return; 17 | 18 | yield element; 19 | } 20 | } 21 | 22 | function* takeLast(source: Iterable, count: number) { 23 | const result: TElement[] = []; 24 | 25 | for (const element of source) { 26 | if (result.length === count) { 27 | result.shift(); 28 | } 29 | 30 | result.push(element); 31 | } 32 | 33 | for (const element of result) { 34 | yield element; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/iterators/takeWhile.ts: -------------------------------------------------------------------------------- 1 | import { iterable } from '../utils/iterable'; 2 | 3 | export function takeWhile( 4 | source: Iterable, 5 | predicate: (element: TElement, index: number) => boolean, 6 | ): Iterable { 7 | return iterable(function* () { 8 | let index = 0; 9 | 10 | for (const element of source) { 11 | if (!predicate(element, index++)) { 12 | return; 13 | } 14 | yield element; 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/reducers/average.ts: -------------------------------------------------------------------------------- 1 | export function average(source: Iterable): number | undefined { 2 | let [s, n] = [0, 0]; 3 | 4 | for (const element of source) { 5 | [s, n] = [s + element, n + 1]; 6 | } 7 | 8 | return n > 0 ? s / n : undefined; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/first.ts: -------------------------------------------------------------------------------- 1 | export function first(source: Iterable): TElement | undefined { 2 | for (const element of source) { 3 | return element; 4 | } 5 | 6 | return undefined; 7 | } 8 | -------------------------------------------------------------------------------- /lib/reducers/indexOf.ts: -------------------------------------------------------------------------------- 1 | export function indexOf( 2 | source: Iterable, 3 | predicate: (element: TElement, index: number) => boolean, 4 | ): number { 5 | let index = -1; 6 | 7 | for (const element of source) { 8 | if (predicate(element, ++index)) { 9 | return index; 10 | } 11 | } 12 | 13 | return -1; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/last.ts: -------------------------------------------------------------------------------- 1 | export function last(source: Iterable): TElement | undefined { 2 | let value: TElement | undefined = undefined; 3 | 4 | for (const element of source) { 5 | value = element; 6 | } 7 | 8 | return value; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/lastIndexOf.ts: -------------------------------------------------------------------------------- 1 | export function lastIndexOf( 2 | source: Iterable, 3 | predicate: (element: TElement, index: number) => boolean, 4 | ): number { 5 | let [result, index] = [-1, -1]; 6 | 7 | for (const element of source) { 8 | if (predicate(element, ++index)) { 9 | result = index; 10 | } 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/length.ts: -------------------------------------------------------------------------------- 1 | export function length(source: Iterable): number { 2 | let cnt = 0; 3 | 4 | for (const _ of source) { 5 | cnt++; 6 | } 7 | 8 | return cnt; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/max.ts: -------------------------------------------------------------------------------- 1 | export function max( 2 | source: Iterable, 3 | compareFn: (element1: T, element2: T) => number, 4 | ): T | undefined { 5 | let result: T | undefined = undefined; 6 | 7 | for (const element of source) { 8 | if (result === undefined || compareFn(element, result) > 0) { 9 | result = element; 10 | } 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/min.ts: -------------------------------------------------------------------------------- 1 | export function min( 2 | source: Iterable, 3 | compareFn: (element1: T, element2: T) => number, 4 | ): T | undefined { 5 | let result: T | undefined = undefined; 6 | 7 | for (const element of source) { 8 | if (result === undefined || compareFn(element, result) < 0) { 9 | result = element; 10 | } 11 | } 12 | 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /lib/reducers/nth.ts: -------------------------------------------------------------------------------- 1 | export function nth(source: Iterable, index: number): TElement | undefined { 2 | return index >= 0 ? 3 | fromFront(source, index) : 4 | fromBack(source, -index); 5 | } 6 | 7 | function fromFront(source: Iterable, index: number): TElement | undefined { 8 | let n = index; 9 | 10 | for (const element of source) { 11 | if (!n--) { 12 | return element; 13 | } 14 | } 15 | 16 | return undefined; 17 | } 18 | 19 | function fromBack(source: Iterable, index: number): TElement | undefined { 20 | const buffer: TElement[] = []; 21 | 22 | for (const element of source) { 23 | buffer.push(element); 24 | 25 | if (buffer.length > index) { 26 | buffer.shift(); 27 | } 28 | } 29 | 30 | return buffer.length === index ? buffer[0] : undefined; 31 | } 32 | -------------------------------------------------------------------------------- /lib/reducers/reduce.ts: -------------------------------------------------------------------------------- 1 | export function reduce( 2 | source: Iterable, 3 | callback: (accumulator: TElement, current: TElement, index: number) => TElement, 4 | ): TElement; 5 | 6 | export function reduce( 7 | source: Iterable, 8 | callback: (accumulator: TAccumulator, current: TElement, index: number) => TAccumulator, 9 | initialValue: TAccumulator, 10 | ): TAccumulator; 11 | 12 | export function reduce( 13 | source: Iterable, 14 | callback: (accumulator: any, current: TElement, index: number) => any, 15 | initialValue?: any, 16 | ): any { 17 | 18 | let [index, accumulator] = [-1, initialValue]; 19 | 20 | for (const element of source) { 21 | accumulator = ++index === 0 && initialValue === undefined ? 22 | element : 23 | callback(accumulator, element, index); 24 | } 25 | 26 | if (initialValue === undefined && index === -1) { 27 | throw new Error('Sequence contains no elements.'); 28 | } 29 | 30 | return accumulator; 31 | } 32 | -------------------------------------------------------------------------------- /lib/reducers/sum.ts: -------------------------------------------------------------------------------- 1 | export function sum(source: Iterable): number | undefined { 2 | let [result, hasElements] = [0, false]; 3 | 4 | for (const element of source) { 5 | [result, hasElements] = [result + element, true]; 6 | } 7 | 8 | return hasElements ? result : undefined; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/toArray.ts: -------------------------------------------------------------------------------- 1 | export function toArray(source: Iterable): TElement[] { 2 | const a: TElement[] = []; 3 | 4 | for (const e of source) { 5 | a.push(e); 6 | } 7 | 8 | return a; 9 | } 10 | -------------------------------------------------------------------------------- /lib/reducers/toGroups.ts: -------------------------------------------------------------------------------- 1 | export function toGroups( 2 | source: Iterable, 3 | keySelector: (element: TElement, index: number) => TKey, 4 | valueSelector: (element: TElement, index: number) => TResult, 5 | ): Map { 6 | 7 | let index = 0; 8 | const map = new Map(); 9 | 10 | for (const element of source) { 11 | const key = keySelector(element, index); 12 | const value = valueSelector(element, index++); 13 | const values = map.get(key); 14 | 15 | if (values !== undefined) { 16 | values.push(value); 17 | } else { 18 | map.set(key, [value]); 19 | } 20 | } 21 | 22 | return map; 23 | } 24 | -------------------------------------------------------------------------------- /lib/reducers/toMap.ts: -------------------------------------------------------------------------------- 1 | export function toMap( 2 | source: Iterable, 3 | keySelector: (element: TElement, index: number) => TKey, 4 | valueSelector: (element: TElement, index: number) => TResult, 5 | ): Map { 6 | 7 | let index = 0; 8 | const map = new Map(); 9 | 10 | for (const element of source) { 11 | const key = keySelector(element, index); 12 | if (map.has(key)) { 13 | throw new Error(`Duplicate map entry key: ${key}.`); 14 | } 15 | 16 | map.set(key, valueSelector(element, index++)); 17 | } 18 | 19 | return map; 20 | } 21 | -------------------------------------------------------------------------------- /lib/reducers/toSet.ts: -------------------------------------------------------------------------------- 1 | export function toSet(source: Iterable): Set { 2 | return new Set(source); 3 | } 4 | -------------------------------------------------------------------------------- /lib/types/IterableCast.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Cast an iterable to a built-in type. 3 | */ 4 | export interface IterableCast extends Iterable { 5 | /** 6 | * Creates an array copy of the sequence. 7 | * @returns T[] 8 | */ 9 | toArray(): T[]; 10 | 11 | /** 12 | * Creates an array copy of transformed values of the sequence. 13 | * @param {(element:T,index:number)=>S} selector element transformation 14 | * @returns S[] 15 | */ 16 | toArray(selector: (element: T, index: number) => S): S[]; 17 | 18 | /** 19 | * Creates a map of elements by a given key. 20 | * @param {(element:T)=>M} keySelector key selector 21 | * @returns Map 22 | */ 23 | toMap( 24 | keySelector: (element: T, index: number) => M): Map; 25 | 26 | /** 27 | * Creates a map of transformed elements by a given key. 28 | * @param {(element:T)=>M} keySelector key selector 29 | * @param {(element:T)=>N} valueSelector element transformation 30 | * @returns Map 31 | */ 32 | toMap( 33 | keySelector: (element: T, index: number) => M, 34 | valueSelector: (element: T, index: number) => N): Map; 35 | 36 | /** 37 | * Creates a map of element groups by a given key. 38 | * @param {(element:T)=>M} keySelector key selector 39 | * @returns Map 40 | * @todo review name 41 | */ 42 | toGroups( 43 | keySelector: (element: T, index: number) => M): Map; 44 | 45 | /** 46 | * Creates a map of transformed element groups by a given key. 47 | * @param {(element:T)=>M} keySelector key selector 48 | * @param {(element:T)=>N} valueSelector element transformation 49 | * @returns Map 50 | */ 51 | toGroups( 52 | keySelector: (element: T, index: number) => M, 53 | valueSelector: (element: T, index: number) => N): Map; 54 | 55 | /** 56 | * Creates a set of elements. 57 | * @returns Set 58 | */ 59 | toSet(): Set; 60 | 61 | /** 62 | * Creates a set of transformed values. 63 | * @param {(element:T,index:number)=>S} selector element transformation 64 | * @returns Set 65 | */ 66 | toSet(selector: (element: T, index: number) => S): Set; 67 | 68 | /** 69 | * Returns a string representing the specified sequence and its elements. 70 | */ 71 | toString(): string; 72 | } 73 | -------------------------------------------------------------------------------- /lib/types/IterableFilter.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from './IterableQuery'; 2 | 3 | /** 4 | * Apply a filter over an iterable and return a new iterable. 5 | */ 6 | export interface IterableFilter extends Iterable { 7 | /** 8 | * Returns a sequence of elements that pass the predicate. 9 | * @param {(element:T,index:number)=>boolean} predicate element predicate 10 | * @returns Iterable 11 | */ 12 | filter(predicate: (element: T, index: number) => boolean): IterableQuery; 13 | 14 | /** 15 | * Returns a specified number of elements from the beginning of sequence. 16 | * If a negative count is specified, returns elements from the end of the sequence. 17 | * @param {number} count 18 | * @returns Iterable 19 | */ 20 | take(count: number): IterableQuery; 21 | 22 | /** 23 | * Returns first elements that pass the predicate. 24 | * @param {(element:T,index:number)=>boolean} predicate element predicate 25 | * @returns Iterable 26 | */ 27 | takeWhile(predicate: (element: T, index: number) => boolean): IterableQuery; 28 | 29 | /** 30 | * Skips the specified number of elements from the beggining of sequence 31 | * and returns the remaining ones. 32 | * If a negative count is specified, skips elements from the end of the sequence. 33 | * @param {number} count 34 | * @returns Iterable 35 | */ 36 | skip(count: number): IterableQuery; 37 | 38 | /** 39 | * Skip first elements that pass the predicate and returns the remaining ones. 40 | * @param {(element:T,index:number)=>boolean} predicate element predicate 41 | * @returns Iterable 42 | */ 43 | skipWhile(predicate: (element: T, index: number) => boolean): IterableQuery; 44 | 45 | /** 46 | * Returns a sequence that represents the range of elements from start to end. 47 | * @param start zero-based index at which to start extraction 48 | * @param end zero-based index before which to end extraction (not including) 49 | * @returns Iterable 50 | */ 51 | slice(start?: number, end?: number): IterableQuery; 52 | 53 | /** 54 | * Returns a sequence that skips elements and/or adds new elements. 55 | * @param start index at which to start skip elements 56 | * @param deleteCount the number of elements to skip 57 | * @param items the elements to add at start index 58 | * @returns Iterable 59 | */ 60 | splice(start: number, deleteCount: number, ...items: T[]): IterableQuery; 61 | } 62 | -------------------------------------------------------------------------------- /lib/types/IterableJoin.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from './IterableQuery'; 2 | 3 | /** 4 | * Join two iterables to produce a new iterable. 5 | */ 6 | export interface IterableJoin extends Iterable { 7 | /** 8 | * Returns a sequence of correlated elements tranformation that match a given key. 9 | * @param {Iterable} other 10 | * @param {(element:T,index:number)=>TKey} leftKeySelector left element key selector 11 | * @param {(element:TRight,index:number)=>TKey} rightKeySelector right element key selector 12 | * @param {(left:T,right:TRight)=>TResult} joinSelector transformation 13 | * @returns Iterable 14 | */ 15 | join( 16 | other: Iterable, 17 | leftKeySelector: (element: T, index: number) => TKey, 18 | rightKeySelector: (element: TRight, index: number) => TKey, 19 | joinSelector: (left: T, right: TRight) => TResult, 20 | ): IterableQuery; 21 | 22 | /** 23 | * Returns a sequence of correlated elements tranformation that match a given key. 24 | * The transformation is called on an undefined right value if there is no match. 25 | * @param {Iterable} other 26 | * @param {(element:T,index:number)=>TKey} leftKeySelector left element key selector 27 | * @param {(element:TRight,index:number)=>TKey} rightKeySelector right element key selector 28 | * @param {(left:T,right?:TRight)=>TResult} joinSelector transformation 29 | * @returns Iterable 30 | */ 31 | leftJoin( 32 | other: Iterable, 33 | leftKeySelector: (element: T, index: number) => TKey, 34 | rightKeySelector: (element: TRight, index: number) => TKey, 35 | joinSelector: (left: T, right?: TRight) => TResult, 36 | ): IterableQuery; 37 | 38 | /** 39 | * Returns a sequence of correlated elements tranformation that match a given key. 40 | * The transformation is called on an undefined left value if there is no match. 41 | * @param other 42 | * @param rightKeySelector right element key selector 43 | * @param leftKeySelector left element key selector 44 | * @param {(right:TRight,left?:T)=>TResult} joinSelector transformation 45 | * @returns Iterable 46 | */ 47 | rightJoin( 48 | other: Iterable, 49 | rightKeySelector: (element: TRight, index: number) => TKey, 50 | leftKeySelector: (element: T, index: number) => TKey, 51 | joinSelector: (right: TRight, left?: T) => TResult, 52 | ): IterableQuery; 53 | 54 | /** 55 | * Returns a sequence of correlated elements where each element from the current sequence 56 | * is matched with zero or more elements from the other sequence. 57 | * @param {Iterable} other 58 | * @param {(element:T,index:number)=>TKey} leftKeySelector left element key selector 59 | * @param {(element:TRight,index:number)=>TKey} rightKeySelector right element key selector 60 | * @param {(left:T,right:TRight)=>TResult} joinSelector transformation 61 | * @returns Iterable 62 | * @todo review name 63 | */ 64 | groupJoin( 65 | other: Iterable, 66 | leftKeySelector: (element: T, index: number) => TKey, 67 | rightKeySelector: (element: TRight, index: number) => TKey, 68 | joinSelector: (left: T, right: TRight[]) => TResult, 69 | ): IterableQuery; 70 | } 71 | -------------------------------------------------------------------------------- /lib/types/IterablePermutation.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from './IterableQuery'; 2 | 3 | /** 4 | * Perform a permutation of elements to produce a new iterable. 5 | */ 6 | export interface IterablePermutation extends Iterable { 7 | /** 8 | * Returns a sequence of sorted elements. 9 | * @returns Iterable 10 | */ 11 | sort(): IterableQuery; 12 | 13 | /** 14 | * Returns a sequence of sorted elements compared by a given comparer. 15 | * @param {(element1:T,element2:T)=>number} compareFn comparer function that returns -1 16 | * for element1element2, 0 for equal values 17 | * @returns Iterable 18 | */ 19 | sort(compareFn: (element1: T, element2: T) => number): IterableQuery; 20 | 21 | /** 22 | * Returns the sequence of elements in a random order. 23 | */ 24 | shuffle(): IterableQuery; 25 | 26 | /** 27 | * Returns a sequence of elements in a reversed order. 28 | * @returns Iterable 29 | */ 30 | reverse(): IterableQuery; 31 | } 32 | -------------------------------------------------------------------------------- /lib/types/IterablePredicate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Apply a predicate over an iterable. 3 | */ 4 | export interface IterablePredicate extends Iterable { 5 | /** 6 | * Tests whether all the elements pass the predicate. 7 | * @param {(element:T, index:number)=>boolean} predicate element predicate 8 | * @returns boolean 9 | */ 10 | every(predicate: (element: T, index: number) => boolean): boolean; 11 | 12 | /** 13 | * Tests whether at least one element passes the predicate. 14 | * @param {(element:T,index:number)=>boolean} predicate element predicate 15 | * @returns boolean 16 | */ 17 | some(predicate: (element: T, index: number) => boolean): boolean; 18 | 19 | /** 20 | * Determines whether the sequence includes a certain element. 21 | * @param element element to search 22 | * @param fromIndex the start index 23 | */ 24 | includes(element: T, fromIndex?: number): boolean; 25 | } 26 | -------------------------------------------------------------------------------- /lib/types/IterableQuery.ts: -------------------------------------------------------------------------------- 1 | import { IterableCast } from './IterableCast'; 2 | import { IterableJoin } from './IterableJoin'; 3 | import { IterablePermutation } from './IterablePermutation'; 4 | import { IterablePredicate } from './IterablePredicate'; 5 | import { IterableFilter } from './IterableFilter'; 6 | import { IterableSet } from './IterableSet'; 7 | import { IterableTransformation } from './IterableTransformation'; 8 | import { IterableValue } from './IterableValue'; 9 | 10 | export interface IterableQuery extends 11 | IterableValue, 12 | IterablePredicate, 13 | IterablePermutation, 14 | IterableFilter, 15 | IterableTransformation, 16 | IterableSet, 17 | IterableJoin, 18 | IterableCast, 19 | Iterable { 20 | 21 | /** 22 | * Returns a sequence of key/value pair for each element and its index. 23 | * @returns Iterable<[number,T]> 24 | */ 25 | entries(): IterableQuery<[number, T]>; 26 | 27 | /** 28 | * Returns a sequence of keys for each index in the source sequence. 29 | * @returns Iterable 30 | */ 31 | keys(): IterableQuery; 32 | 33 | /** 34 | * Returns a sequence of values for each index in the source sequence. 35 | * @returns Iterable 36 | */ 37 | values(): IterableQuery; 38 | 39 | /** 40 | * Concatenates the sequence with another one. 41 | * @param {Iterable | T} other 42 | * @returns Iterable 43 | */ 44 | concat(other: T | Iterable): IterableQuery; 45 | 46 | /** 47 | * Returns a sequence with given elements at the beginning. 48 | * @param {Iterable | T} other 49 | * @returns Iterable 50 | * @todo review name 51 | */ 52 | prepend(other: Iterable | T): IterableQuery; 53 | 54 | /** 55 | * Returns a sequence filled from 0-th index to the end of the sequence with a static value. 56 | * The end index is not included. 57 | * @param value value to fill 58 | * @returns Iterable 59 | */ 60 | fill(value: T): IterableQuery; 61 | 62 | /** 63 | * Returns a sequence filled from a start index to the end of the sequence with a static value. 64 | * The end index is not included. 65 | * @param value value to fill 66 | * @param start start index, defaults to 0 67 | * @returns Iterable 68 | */ 69 | fill(value: T, start: number): IterableQuery; 70 | 71 | /** 72 | * Returns a sequence filled from a start index to an end index with a static value. 73 | * The end index is not included. 74 | * @param value value to fill 75 | * @param start start index, defaults to 0 76 | * @param end end index, defaults to sequence count 77 | * @returns Iterable 78 | */ 79 | fill(value: T, start: number, end: number): IterableQuery; 80 | } 81 | -------------------------------------------------------------------------------- /lib/types/IterableSet.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from './IterableQuery'; 2 | 3 | /** 4 | * Produce a new iterable that is a set iterable of unique elements. 5 | */ 6 | export interface IterableSet extends Iterable { 7 | /** 8 | * Returns a sequence of unique elements. 9 | * @returns Iterable 10 | */ 11 | distinct(): IterableQuery; 12 | 13 | /** 14 | * Returns a sequence of unique element transformations. 15 | * @param {(element:T)=>S} selector element transformation 16 | * @returns Iterable 17 | */ 18 | distinct(selector: (element: T) => S): IterableQuery; 19 | 20 | /** 21 | * Returns a sequence of unique elements not contained in a given sequence. 22 | * @param {Iterable} other element transformation 23 | * @returns Iterable 24 | */ 25 | exclude(other: Iterable): IterableQuery; 26 | 27 | /** 28 | * Returns a sequence of unique elements not contained in a given sequence 29 | * using a transformation for value comparisons. 30 | * @param {Iterable} other items to compare and exclude 31 | * @param {(element: T)=>S} selector element transformation 32 | * @returns Iterable 33 | */ 34 | exclude(other: Iterable, selector: (element: T) => S): IterableQuery; 35 | 36 | /** 37 | * Returns a set intersection with a given sequence. 38 | * @param {Iterable} other 39 | * @returns Iterable 40 | */ 41 | intersect(other: Iterable): IterableQuery; 42 | 43 | /** 44 | * Returns a set intersection with a given sequence using a transformation for comparisons. 45 | * @param {Iterable} other 46 | * @pa{(element: T)=>S} element transformation 47 | * @returns Iterable 48 | */ 49 | intersect(other: Iterable, selector: (element: T) => S): IterableQuery; 50 | 51 | /** 52 | * Returns a set union with a given sequence. 53 | * @param {Iterable} other 54 | * @returns Iterable 55 | */ 56 | union(other: Iterable): IterableQuery; 57 | 58 | /** 59 | * Returns a set union with a given sequence using a transformation for comparisons. 60 | * @param {Iterable} other 61 | * @pa{(element: T)=>S} element transformation 62 | * @returns Iterable 63 | */ 64 | union(other: Iterable, selector: (element: T) => S): IterableQuery; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /lib/types/IterableTransformation.ts: -------------------------------------------------------------------------------- 1 | import { IterableQuery } from './IterableQuery'; 2 | 3 | /** 4 | * Transform an iterable to a new iterable. 5 | */ 6 | export interface IterableTransformation extends Iterable { 7 | /** 8 | * Returns a sequence of transformed values. 9 | * @param {(element:T,index:number)=>S} selector element transformation 10 | * @returns Iterable 11 | */ 12 | map(selector: (element: T, index: number) => S): IterableQuery; 13 | 14 | /** 15 | * Returns a sequence with all sub-sequences concatenated. 16 | * @param {(element:T,index:number)=>Iterable} selector sub-sequence 17 | * @returns Iterable 18 | */ 19 | flat(selector: (element: T, index: number) => Iterable): IterableQuery; 20 | 21 | /** 22 | * Groups elements by a given key. 23 | * @param {(element:T,index:number)=>K} keySelector key selector 24 | * @returns Iterable 25 | */ 26 | groupBy( 27 | keySelector: (element: T, index: number) => K): IterableQuery<[K, IterableQuery]>; 28 | 29 | /** 30 | * Groups elements by a given key and applies a transformation over the elements. 31 | * @param {(element:T,index:number)=>K} keySelector key selector 32 | * @param {(element:T,index:number)=>E} valueSelector element transformation 33 | * @returns Iterable 34 | */ 35 | groupBy( 36 | keySelector: (element: T, index: number) => K, 37 | valueSelector: (element: T, index: number) => E): IterableQuery<[K, IterableQuery]>; 38 | } 39 | 40 | /** 41 | * An iterable group representation. 42 | */ 43 | export interface IterableQueryGroup extends IterableQuery { 44 | /** 45 | * Current group key. 46 | */ 47 | readonly key: K; 48 | } 49 | -------------------------------------------------------------------------------- /lib/types/IterableValue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate a scalar value over an iterable. 3 | */ 4 | export interface IterableValue extends Iterable { 5 | /** 6 | * Returns the element at a specified index. 7 | * For a negative index returns the element from the end of the sequence. 8 | * If index is out of the range, returns undefined. 9 | * @param {number} index element's index 10 | * @returns T 11 | */ 12 | nth(index: number): T | undefined; 13 | 14 | /** 15 | * Returns the first index at which a given element can be found. 16 | * If not present, returns -1. 17 | * @param {T} element element to search 18 | * @returns number 19 | */ 20 | indexOf(element: T): number; 21 | 22 | /** 23 | * Returns the first index at which a given element can be found. 24 | * If not present, returns -1. 25 | * @param {T} element element to search 26 | * @param fromIndex the start index 27 | * @returns number 28 | */ 29 | indexOf(element: T, fromIndex: number): number; 30 | 31 | /** 32 | * Finds the first index at which a given element satisfies the specified predicate. 33 | * If not present, returns -1. 34 | * @param {(element:T)=>boolean} predicate element predicate 35 | * @returns number 36 | */ 37 | findIndex(predicate: (element: T, index: number) => boolean): number; 38 | 39 | /** 40 | * Returns the last index at which a given element can be found. 41 | * If not present, returns -1. 42 | * @param {T} element element to search 43 | * @returns number 44 | */ 45 | lastIndexOf(element: T): number; 46 | 47 | /** 48 | * Returns the last index at which a given element can be found. 49 | * If not present, returns -1. 50 | * @param {T} element element to search 51 | * @param fromIndex the start index 52 | * @returns number 53 | */ 54 | lastIndexOf(element: T, fromIndex: number): number; 55 | 56 | /** 57 | * Finds the last index at which a given element satisfies the specified predicate. 58 | * If not present, returns -1. 59 | * @param {T} element element to search 60 | * @returns number 61 | */ 62 | findLastIndex(predicate: (element: T, index: number) => boolean): number; 63 | 64 | /** 65 | * Returns the number of elements. 66 | * @returns number 67 | */ 68 | length(): number; 69 | 70 | /** 71 | * Returns the number of elements matching the specified predicate. 72 | * @param {(element:T,index:number)=>boolean} predicate element predicate 73 | * @returns number 74 | */ 75 | length(predicate: (element: T, index: number) => boolean): number; 76 | 77 | /** 78 | * Returns the first element. 79 | * For an empty sequence returns undefined. 80 | * @returns T 81 | */ 82 | first(): T | undefined; 83 | 84 | /** 85 | * Finds the first element that satisfies the specified predicate. 86 | * If no element satisfies the predicate, returns undefined. 87 | * @param {(element:T,index:number)=>boolean} predicate element predicate 88 | * @returns T 89 | */ 90 | find(predicate: (element: T, index: number) => boolean): T | undefined; 91 | 92 | /** 93 | * Returns the last element. 94 | * For an empty sequence returns undefined. 95 | * @returns T 96 | */ 97 | last(): T | undefined; 98 | 99 | /** 100 | * Finds the last element that satisfies the specified predicate. 101 | * If no element satisfies the predicate, returns undefined. 102 | * @param {(element:T,index:number)=>boolean} predicate element predicate 103 | * @returns T 104 | */ 105 | findLast(predicate: (element: T, index: number) => boolean): T | undefined; 106 | 107 | /** 108 | * Returns the average value. 109 | * If sequence is empty, returns undefined. 110 | * @returns number 111 | */ 112 | average(): number | undefined; 113 | 114 | /** 115 | * Returns the average value over a sequence of transformed values. 116 | * If sequence is empty, returns undefined. 117 | * @param {(element:T,index:number)=>number} selector element transformation 118 | * @returns number 119 | */ 120 | average(selector: (element: T, index: number) => number): number | undefined; 121 | 122 | /** 123 | * Returns the minimum value. 124 | * If sequence is empty, returns undefined. 125 | * @returns T 126 | */ 127 | min(): T | undefined; 128 | 129 | /** 130 | * Returns the minimum value from a sequence using a comparer function. 131 | * If sequence is empty, returns undefined. 132 | * @param {(element1:T,element2:T)=>number} compareFn comparer function that returns -1 133 | * for element1element2, 0 for equal values 134 | * @returns T 135 | */ 136 | min(compareFn: (element1: T, element2: T) => number): T | undefined; 137 | 138 | /** 139 | * Returns the maximum value. 140 | * If sequence is empty, returns undefined. 141 | * @returns T 142 | */ 143 | max(): T | undefined; 144 | 145 | /** 146 | * Returns the maximum value from a sequence using a compare function. 147 | * If sequence is empty, returns undefined. 148 | * @param {(element1:T,element2:T)=>number} compareFn comparer function that returns -1 149 | * for element1element2, 0 for equal values 150 | * @returns T 151 | */ 152 | max(compareFn: (element1: T, element2: T) => number): T | undefined; 153 | 154 | /** 155 | * Returns the sum of all elements. 156 | * If sequence is empty, returns undefined. 157 | * @returns number 158 | */ 159 | sum(): number | undefined; 160 | 161 | /** 162 | * Returns the sum of elements from a sequence of transformed values. 163 | * If sequence is empty, returns undefined. 164 | * @param {(element:T,index:number)=>number} selector element transformation 165 | * @returns number 166 | */ 167 | sum(selector: (element: T, index: number) => number): number | undefined; 168 | 169 | /** 170 | * Applies a function against an accumulator and each element to reduce it to a single value 171 | * (from left to right). 172 | * @param {(accumulator:TResult,current:T,index:number)=>TResult} callback accumulator function 173 | * @returns TResult 174 | */ 175 | reduce( 176 | callback: (accumulator: T, current: T, index: number) => T, 177 | ): T; 178 | 179 | /** 180 | * Applies a function against an accumulator and each element to reduce it to a single value 181 | * (from left to right). 182 | * @param {(accumulator:S,current:T,index:number)=>S} callback accumulator function 183 | * @param {S} initialValue initial value 184 | * @returns S 185 | */ 186 | reduce( 187 | callback: (accumulator: S, current: T, index: number) => S, 188 | initialValue: S, 189 | ): S; 190 | 191 | /** 192 | * Applies a function against an accumulator and each element to reduce it to a single value 193 | * (from rigth to left). 194 | * @param {(accumulator:TResult,current:T,index:number)=>TResult} callback accumulator function 195 | * @returns TResult 196 | */ 197 | reduceRight( 198 | callback: (accumulator: TResult, current: T, index: number) => TResult, 199 | ): TResult; 200 | 201 | /** 202 | * Applies a function against an accumulator and each element to reduce it to a single value 203 | * (from rigth to left). 204 | * @param {(accumulator:S,current:T,index:number)=>S} callback accumulator function 205 | * @param {S} initialValue initial value 206 | * @returns S 207 | */ 208 | reduceRight( 209 | callback: (accumulator: S, current: T, index: number) => S, 210 | initialValue: S, 211 | ): S; 212 | 213 | /** 214 | * Runs through every element and applies a given function. 215 | * @param {(element:T,index:number)=>void} action function to apply on each element 216 | * @returns void 217 | */ 218 | forEach(action: (element: T, index: number) => void): void; 219 | } 220 | -------------------------------------------------------------------------------- /lib/utils/isIterable.ts: -------------------------------------------------------------------------------- 1 | export function isIterable(item: any): item is Iterable { 2 | return typeof (>item)[Symbol.iterator] === 'function'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/utils/iterable.ts: -------------------------------------------------------------------------------- 1 | export function iterable(target: () => IterableIterator): Iterable { 2 | return { 3 | [Symbol.iterator]() { 4 | return target(); 5 | }, 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/iterator.ts: -------------------------------------------------------------------------------- 1 | export function iterator(source: Iterable): Iterator { 2 | return source[Symbol.iterator](); 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itiriri", 3 | "version": "2.0.1", 4 | "license": "MIT", 5 | "description": "A library built for ES6 iteration protocol.", 6 | "private": true, 7 | "scripts": { 8 | "test": "gulp test", 9 | "coverage": "istanbul cover node_modules/mocha/bin/_mocha ./build/compiled/test ./build/compiled/lib -- --recursive" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/labs42io/itiriri.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/labs42io/itiriri/issues" 17 | }, 18 | "keywords": [ 19 | "iterator", 20 | "iterable", 21 | "filter", 22 | "map", 23 | "query", 24 | "collections", 25 | "deferred", 26 | "lazy" 27 | ], 28 | "author": { 29 | "name": "Labs42", 30 | "url": "https://labs42.io" 31 | }, 32 | "contributors": [ 33 | "Dumitru Deveatii ", 34 | "Vlad Negura " 35 | ], 36 | "dependencies": {}, 37 | "devDependencies": { 38 | "@types/chai": "4.2.11", 39 | "@types/chai-as-promised": "7.1.2", 40 | "@types/mocha": "7.0.2", 41 | "@types/node": "^14.0.9", 42 | "benchmark": "2.1.4", 43 | "browserify": "16.5.1", 44 | "chai": "4.2.0", 45 | "chai-as-promised": "7.1.1", 46 | "chai-spies": "1.0.0", 47 | "del": "5.1.0", 48 | "gulp": "4.0.2", 49 | "gulp-mocha": "7.0.2", 50 | "gulp-replace": "1.0.0", 51 | "gulp-shell": "0.8.0", 52 | "gulp-sourcemaps": "2.6.5", 53 | "gulp-tslint": "8.1.4", 54 | "gulp-typescript": "5.0.1", 55 | "gulp-uglify-es": "2.0.0", 56 | "gulpclass": "0.2.0", 57 | "istanbul": "0.4.5", 58 | "mocha": "7.2.0", 59 | "mocha-lcov-reporter": "1.3.0", 60 | "ts-node": "8.10.2", 61 | "tslint": "6.1.2", 62 | "tslint-config-airbnb": "5.11.2", 63 | "typescript": "3.9.3", 64 | "vinyl-buffer": "1.0.1", 65 | "vinyl-source-stream": "2.0.0" 66 | } 67 | } -------------------------------------------------------------------------------- /test/helpers/SpyIterable.ts: -------------------------------------------------------------------------------- 1 | export class SpyIterable implements Iterable { 2 | private iterations: number = 0; 3 | constructor(private readonly source: Iterable) { } 4 | 5 | get iterated() { 6 | return this.iterations > 0; 7 | } 8 | 9 | get iteratedOnce() { 10 | return this.iterations === 1; 11 | } 12 | 13 | [Symbol.iterator](): Iterator { 14 | this.iterations++; 15 | return this.source[Symbol.iterator](); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/helpers/generators.ts: -------------------------------------------------------------------------------- 1 | export function* numbers(offset = 0, step = 1) { 2 | let i = offset; 3 | while (1) { 4 | yield i; 5 | i += step; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/iterators/concat.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { concat } from '../../lib/iterators/concat'; 3 | import { iterator } from '../../lib/utils/iterator'; 4 | 5 | describe('iterators/concat', () => { 6 | describe('when called multiple times', () => { 7 | it('Should return new iterator on each call', () => { 8 | const left = [1, 2]; 9 | const right = [3, 4, 5]; 10 | const source = concat(left, right); 11 | 12 | expect(iterator(source)).not.equals(iterator(source)); 13 | }); 14 | }); 15 | 16 | describe('When left and right are empty', () => { 17 | it('Should return a completed iterator', () => { 18 | const left = []; 19 | const right = []; 20 | const result = iterator(concat(left, right)); 21 | 22 | expect(result.next()) 23 | .to.have.property('done') 24 | .that.is.true; 25 | }); 26 | }); 27 | 28 | describe('When only left is empty', () => { 29 | it('Should return items from right', () => { 30 | const left = []; 31 | const right = [1, 2, 3]; 32 | const it = iterator(concat(left, right)); 33 | 34 | let current = it.next(); 35 | expect(current.value).to.equal(1); 36 | expect(current.done).to.be.false; 37 | 38 | current = it.next(); 39 | expect(current.value).to.equal(2); 40 | expect(current.done).to.be.false; 41 | 42 | current = it.next(); 43 | expect(current.value).to.equal(3); 44 | expect(current.done).to.be.false; 45 | 46 | current = it.next(); 47 | expect(current.value).to.be.undefined; 48 | expect(current.done).to.be.true; 49 | }); 50 | }); 51 | 52 | describe('When only right is empty', () => { 53 | it('Should return items from left', () => { 54 | const left = [1, 2, 3]; 55 | const right = []; 56 | const it = iterator(concat(left, right)); 57 | 58 | let current = it.next(); 59 | expect(current.value).to.equal(1); 60 | expect(current.done).to.be.false; 61 | 62 | current = it.next(); 63 | expect(current.value).to.equal(2); 64 | expect(current.done).to.be.false; 65 | 66 | current = it.next(); 67 | expect(current.value).to.equal(3); 68 | expect(current.done).to.be.false; 69 | 70 | current = it.next(); 71 | expect(current.value).to.be.undefined; 72 | expect(current.done).to.be.true; 73 | }); 74 | }); 75 | 76 | describe('When left and right have elements', () => { 77 | it('Should iterate over concatenated elements', () => { 78 | const left = [1]; 79 | const right = [2, 3]; 80 | const it = iterator(concat(left, right)); 81 | 82 | let current = it.next(); 83 | expect(current.value).to.equal(1); 84 | expect(current.done).to.be.false; 85 | 86 | current = it.next(); 87 | expect(current.value).to.equal(2); 88 | expect(current.done).to.be.false; 89 | 90 | current = it.next(); 91 | expect(current.value).to.equal(3); 92 | expect(current.done).to.be.false; 93 | 94 | current = it.next(); 95 | expect(current.value).to.be.undefined; 96 | expect(current.done).to.be.true; 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/iterators/distinct.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { distinct } from '../../lib/iterators/distinct'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | import { iterator } from '../../lib/utils/iterator'; 5 | 6 | describe('iterators/distinct', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const source = [1, 2, 3]; 10 | const result = distinct(source, x => x); 11 | 12 | expect(iterator(result)).not.equals(iterator(result)); 13 | }); 14 | }); 15 | 16 | describe('When source is empty', () => { 17 | it('Should return completed iterator', () => { 18 | const source = []; 19 | const iterator = distinct(source, x => x); 20 | 21 | expect(toArray(iterator)).to.deep.equal([]); 22 | }); 23 | }); 24 | 25 | describe('When source has unique elements', () => { 26 | it('Should return the elements', () => { 27 | const arr = [1, 2, 3]; 28 | const iterator = distinct(arr, x => x); 29 | const result = toArray(iterator); 30 | 31 | expect(result).to.deep.equal(arr); 32 | }); 33 | }); 34 | 35 | describe('When source has unique element keys', () => { 36 | it('Should return the elements', () => { 37 | const arr = [{ p: 1 }, { p: 2 }, { p: 3 }]; 38 | const iterator = distinct(arr, x => x.p); 39 | const result = toArray(iterator); 40 | 41 | expect(result).to.deep.equal(arr); 42 | }); 43 | }); 44 | 45 | describe('When source has all elements same', () => { 46 | it('Should return the unique element', () => { 47 | const arr = [42, 42, 42]; 48 | const iterator = distinct(arr, x => x); 49 | const result = toArray(iterator); 50 | 51 | expect(result).to.deep.equal([42]); 52 | }); 53 | }); 54 | 55 | describe('When source has duplicate elements', () => { 56 | it('Should return unique elements', () => { 57 | const arr = [1, 42, 2, 42, 42]; 58 | const iterator = distinct(arr, x => x); 59 | const result = toArray(iterator); 60 | 61 | expect(result).to.deep.equal([1, 42, 2]); 62 | }); 63 | }); 64 | 65 | describe('When source has all element keys same', () => { 66 | it('Should return the unique element', () => { 67 | const elem = { p: 42 }; 68 | const arr = [elem, elem, elem]; 69 | const iterator = distinct(arr, x => x.p); 70 | const result = toArray(iterator); 71 | 72 | expect(result).to.deep.equal([elem]); 73 | }); 74 | }); 75 | 76 | describe('When source has duplicate element keys', () => { 77 | it('Should return unique elements by their first occurence', () => { 78 | const elem1 = { p: 1 }; 79 | const elem42 = { p: 42 }; 80 | const elem2 = { p: 2 }; 81 | const arr = [elem1, elem42, elem2, { p: 42 }, { p: 42 }]; 82 | const iterator = distinct(arr, x => x.p); 83 | const result = toArray(iterator); 84 | 85 | expect(result).to.deep.equal([elem1, elem42, elem2]); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/iterators/exclude.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { exclude } from '../../lib/iterators/exclude'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | import { iterator } from '../../lib/utils/iterator'; 5 | 6 | describe('iterators/exclude', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const left = [1, 2]; 10 | const right = [3, 4, 5]; 11 | 12 | expect(exclude(left, right, x => x)).not.equals(exclude(left, right, x => x)); 13 | }); 14 | }); 15 | 16 | describe('When source is empty', () => { 17 | it('Should return completed iterator', () => { 18 | const left = []; 19 | const right = []; 20 | const it = iterator(exclude(left, right, x => x)); 21 | 22 | expect(it.next()).to.have.property('done').that.is.true; 23 | }); 24 | 25 | it('Should return completed iterator for non-empty exclusions', () => { 26 | const left = []; 27 | const right = [1, 2, 3]; 28 | const it = iterator(exclude(left, right, x => x)); 29 | 30 | expect(it.next()).to.have.property('done').that.is.true; 31 | }); 32 | }); 33 | 34 | describe('When source has elemements', () => { 35 | describe('And exclusion is empty', () => { 36 | it('Should return elements from source', () => { 37 | const left = [1, 2, 3]; 38 | const right = []; 39 | const iterator = exclude(left, right, x => x); 40 | const result = toArray(iterator); 41 | 42 | expect(result).to.deep.equal([1, 2, 3]); 43 | }); 44 | }); 45 | 46 | describe('And is disjoint with exclusion', () => { 47 | it('Should return elements from source', () => { 48 | const left = [1, 2, 3]; 49 | const right = [4, 5]; 50 | const iterator = exclude(left, right, x => x); 51 | const result = toArray(iterator); 52 | 53 | expect(result).to.deep.equal([1, 2, 3]); 54 | }); 55 | }); 56 | 57 | describe('And source has same elements', () => { 58 | it('Should return empty iterator', () => { 59 | const left = [1, 2, 3]; 60 | const right = [1, 2, 3]; 61 | const it = iterator(exclude(left, right, x => x)); 62 | 63 | expect(it.next()).to.have.property('done').that.is.true; 64 | }); 65 | }); 66 | 67 | describe('And source has some elements in common', () => { 68 | it('Should return correct result', () => { 69 | const left = [1, 2, 3]; 70 | const right = [2, 3, 4]; 71 | const iterator = exclude(left, right, x => x); 72 | const result = toArray(iterator); 73 | 74 | expect(result).to.deep.equal([1]); 75 | }); 76 | }); 77 | 78 | describe('And source has some elements in common', () => { 79 | it('Should return correct result for a key selector', () => { 80 | const elem1 = { p: 1 }; 81 | const elem2 = { p: 2 }; 82 | const elem3 = { p: 3 }; 83 | const elem4 = { p: 4 }; 84 | const left = [elem1, elem2, elem3, { p: 1 }]; 85 | const right = [{ p: 2 }, elem3, elem4]; 86 | const iterator = exclude(left, right, x => x.p); 87 | const result = toArray(iterator); 88 | 89 | expect(result).to.deep.equal([elem1, { p: 1 }]); 90 | }); 91 | }); 92 | 93 | describe('And source has some duplicate elements in common', () => { 94 | it('Should return correct result', () => { 95 | const left = [1, 1, 2, 3, 3]; 96 | const right = [2, 2, 3, 3, 4]; 97 | const iterator = exclude(left, right, x => x); 98 | const result = toArray(iterator); 99 | 100 | expect(result).to.deep.equal([1, 1]); 101 | }); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/iterators/fill.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { iterator } from '../../lib/utils/iterator'; 3 | import { fill } from '../../lib/iterators/fill'; 4 | import { toArray } from '../../lib/reducers/toArray'; 5 | 6 | describe('iterators/fill', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const left = [1, 2]; 10 | const right = [3, 4, 5]; 11 | const source = fill(left, right); 12 | 13 | expect(iterator(source)).not.equals(iterator(source)); 14 | }); 15 | }); 16 | 17 | describe('When start is greater than end', () => { 18 | it('Should return the same source', () => { 19 | const source = [4, 5, 3, 1, 2]; 20 | const iter = fill(source, 10, 10, 9); 21 | 22 | expect(toArray(iter)).to.be.deep.equal([4, 5, 3, 1, 2]); 23 | }); 24 | }); 25 | 26 | describe('When called with negative start', () => { 27 | it('Should throw exception', () => { 28 | const source = []; 29 | 30 | expect(() => fill(source, 0, -1)).to.throw(Error); 31 | }); 32 | }); 33 | 34 | describe('When called with negative end', () => { 35 | it('Should throw exception', () => { 36 | const source = []; 37 | 38 | expect(() => fill(source, 0, 1, -1)).to.throw(Error); 39 | }); 40 | }); 41 | 42 | describe('When called without start and end', () => { 43 | it('Should fill the whole array', () => { 44 | const source = [1, 3, 4, 2]; 45 | const iter = fill(source, 0); 46 | 47 | expect(toArray(iter)).to.be.deep.equal([0, 0, 0, 0]); 48 | }); 49 | }); 50 | 51 | describe('When called with start only', () => { 52 | it('Should return array of 4 elements', () => { 53 | const source = [1, 3, 4, 2]; 54 | const iter = fill(source, 0, 1); 55 | 56 | expect(toArray(iter)).to.be.deep.equal([1, 0, 0, 0]); 57 | }); 58 | }); 59 | 60 | describe('When called with start and end', () => { 61 | it('Should return array of 5 elements', () => { 62 | const source = [1, 3, 4, 2, 1]; 63 | const iter = fill(source, 0, 1, 3); 64 | 65 | expect(toArray(iter)).to.be.deep.equal([1, 0, 0, 2, 1]); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/iterators/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { filter } from '../../lib/iterators/filter'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | import { iterator } from '../../lib/utils/iterator'; 5 | 6 | describe('iterators/filter', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const source = [1, 2, 3]; 10 | const result = filter(source, _ => true); 11 | 12 | expect(iterator(result)).not.equals(iterator(result)); 13 | }); 14 | }); 15 | 16 | describe('When source is empty', () => { 17 | it('Should return completed iterator', () => { 18 | const source = []; 19 | const iterator = filter(source, _ => true); 20 | 21 | expect(toArray(iterator)).to.deep.equal([]); 22 | }); 23 | }); 24 | 25 | describe('When predicate is always true', () => { 26 | it('Should return all elements', () => { 27 | const source = [1, 2, 3]; 28 | const iterator = filter(source, _ => true); 29 | const result = toArray(iterator); 30 | 31 | expect(result).to.deep.equal([1, 2, 3]); 32 | }); 33 | }); 34 | 35 | describe('When predicate is always false', () => { 36 | it('Should return completed iterator', () => { 37 | const source = [1, 2, 3]; 38 | const iterator = filter(source, _ => false); 39 | 40 | expect(toArray(iterator)).to.deep.equal([]); 41 | }); 42 | }); 43 | 44 | describe('When predicate matches odd indexes', () => { 45 | it('Should return elements', () => { 46 | const source = [1, 42, 3, 4242]; 47 | const iterator = filter(source, (_, i) => i % 2 === 1); 48 | const result = toArray(iterator); 49 | 50 | expect(result).to.deep.equal([42, 4242]); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/iterators/flat.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { flat } from '../../lib/iterators/flat'; 3 | import { iterator } from '../../lib/utils/iterator'; 4 | import { toArray } from '../../lib/reducers/toArray'; 5 | 6 | describe('iterators/flat', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const left = [1, 2, 3]; 10 | const right = [4, 5]; 11 | const source = [left, right]; 12 | 13 | expect(flat(source)).not.equals(flat(source)); 14 | }); 15 | }); 16 | 17 | describe('When source is empty', () => { 18 | it('Should return completed iterator', () => { 19 | const source = []; 20 | const it = iterator(flat(source)); 21 | 22 | expect(it.next()) 23 | .to.have.property('done') 24 | .that.is.true; 25 | }); 26 | }); 27 | 28 | describe('When left source is empty', () => { 29 | it('Should return elements from right source', () => { 30 | const left = []; 31 | const right = [4, 5]; 32 | const source = [left, right]; 33 | const iterator = flat(source); 34 | const result = toArray(iterator); 35 | 36 | expect(result).to.deep.equal([4, 5]); 37 | }); 38 | }); 39 | 40 | describe('When right source is empty', () => { 41 | it('Should return elements from left source', () => { 42 | const left = [1, 2, 3]; 43 | const right = []; 44 | const source = [left, right]; 45 | const iterator = flat(source); 46 | const result = toArray(iterator); 47 | 48 | expect(result).to.deep.equal([1, 2, 3]); 49 | }); 50 | }); 51 | 52 | describe('When has multiple iterables with elements', () => { 53 | it('Should return elements from left source', () => { 54 | const source1 = [1, 2, 3]; 55 | const source2 = [4, 5, 1]; 56 | const source3 = [42]; 57 | 58 | const source = [source1, source2, source3]; 59 | const iterator = flat(source); 60 | const result = toArray(iterator); 61 | 62 | expect(result).to.deep.equal([1, 2, 3, 4, 5, 1, 42]); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/iterators/groupJoin.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { groupJoin } from '../../lib/iterators/groupJoin'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/groupJoin', () => { 6 | describe('When called on empty sources', () => { 7 | it('Should return empty sources', () => { 8 | const source = []; 9 | const others = []; 10 | const iterator = groupJoin(source, others, x => x, x => x, x => x); 11 | 12 | expect(toArray(iterator)).to.be.deep.equal([]); 13 | }); 14 | }); 15 | 16 | describe('When called on some sources', () => { 17 | it('Should return 3 grupped elemnts', () => { 18 | const source = [1, 2, 3]; 19 | const others = [2, 2, 3, 3, 3, 4, 4]; 20 | const iterator = groupJoin(source, others, x => x, x => x, (x, y) => ({ x, y })); 21 | 22 | expect(toArray(iterator)).to.be.deep.equal([ 23 | { x: 1, y: [] }, 24 | { x: 2, y: [2, 2] }, 25 | { x: 3, y: [3, 3, 3] }, 26 | ]); 27 | }); 28 | 29 | it('Should return 2 grupped elemnts', () => { 30 | const source = [ 31 | { val: 2, other: 'asdf' }, 32 | { val: 3, other: 'ad' }, 33 | ]; 34 | const others = [ 35 | { val: 3, y: 'a' }, 36 | { val: 3, y: 'b' }, 37 | ]; 38 | const iterator = groupJoin( 39 | source, 40 | others, 41 | x => x.val, 42 | x => x.val, 43 | (x, y) => ({ y, x: x.val }), 44 | ); 45 | 46 | expect(toArray(iterator)).to.be.deep.equal([ 47 | { x: 2, y: [] }, 48 | { x: 3, y: [{ val: 3, y: 'a' }, { val: 3, y: 'b' }] }, 49 | ]); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/iterators/intersect.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { intersect } from '../../lib/iterators/intersect'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/intersect', () => { 6 | describe('When called on empty sources', () => { 7 | it('Should return empty source', () => { 8 | const source = []; 9 | const others = []; 10 | const iterator = intersect(source, others, x => x); 11 | 12 | expect(toArray(iterator)).to.be.deep.equal([]); 13 | }); 14 | }); 15 | 16 | describe('When called on some sources', () => { 17 | it('Should return all 3 elements', () => { 18 | const source = [3, 4, 5]; 19 | const others = [3, 4, 5]; 20 | const iterator = intersect(source, others, x => x); 21 | 22 | expect(toArray(iterator)).to.be.deep.equal([3, 4, 5]); 23 | }); 24 | 25 | it('Should return all 4 elements', () => { 26 | const source = [3, 44, 5, 1]; 27 | const others = [3, 1, 5, 44]; 28 | const iterator = intersect(source, others, x => x); 29 | 30 | expect(toArray(iterator)).to.be.deep.equal([3, 44, 5, 1]); 31 | }); 32 | 33 | it('Should return 2 elements', () => { 34 | const source = [3, 44, 5, 1]; 35 | const others = [3, 7, 6, 44]; 36 | const iterator = intersect(source, others, x => x); 37 | 38 | expect(toArray(iterator)).to.be.deep.equal([3, 44]); 39 | }); 40 | 41 | it('Should return 1 element', () => { 42 | const source = [3, 44, 5, 1, 20]; 43 | const others = [1, 1, 51, 444, 2]; 44 | const iterator = intersect(source, others, x => x); 45 | 46 | expect(toArray(iterator)).to.be.deep.equal([1]); 47 | }); 48 | 49 | it('Should return empty source', () => { 50 | const source = [3, 44, 5, 1]; 51 | const others = [11, 11, 51, 414]; 52 | const iterator = intersect(source, others, x => x); 53 | 54 | expect(toArray(iterator)).to.be.deep.equal([]); 55 | }); 56 | 57 | it('Should return empty source', () => { 58 | const source = [{ x: 1, y: 'aasdf' }, { x: 2, y: 'fdasd' }]; 59 | const others = [{ x: 2, y: 'asdf' }, { x: 3, y: 'asdf' }]; 60 | const iterator = intersect(source, others, x => x.x); 61 | 62 | expect(toArray(iterator)).to.be.deep.equal([{ x: 2, y: 'fdasd' }]); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/iterators/join.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { join } from '../../lib/iterators/join'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/join', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return empty source', () => { 8 | const source = []; 9 | const others = []; 10 | const iterator = join(source, others, x => x, x => x, (e1, e2) => ({ e1, e2 })); 11 | 12 | expect(toArray(iterator)).to.be.deep.equal([]); 13 | }); 14 | }); 15 | 16 | describe('When called on some source', () => { 17 | it('Should return common values', () => { 18 | const source = [1, 2, 3, 4]; 19 | const others = [3, 4, 5, 6]; 20 | const iterator = join(source, others, x => x, x => x, x => x); 21 | 22 | expect(toArray(iterator)).to.be.deep.equal([3, 4]); 23 | }); 24 | 25 | it('Should return objects with common property', () => { 26 | const source = [ 27 | { name: 'Football', awesomeness: 20 }, 28 | { name: 'Chess', awesomeness: 30 }, 29 | { name: 'Hockey', awesomeness: 40 }, 30 | ]; 31 | const others = [ 32 | { name: 'Russia', awesomeness: 30 }, 33 | { name: 'Norway', awesomeness: 20 }, 34 | { name: 'France', awesomeness: 40 }, 35 | ]; 36 | const iterator = join( 37 | source, 38 | others, 39 | x => x.awesomeness, 40 | x => x.awesomeness, 41 | (e1, e2) => `Playing ${e1.name} in ${e2.name}`, 42 | ); 43 | 44 | expect(toArray(iterator)).to.be.deep.equal([ 45 | 'Playing Football in Norway', 46 | 'Playing Chess in Russia', 47 | 'Playing Hockey in France', 48 | ]); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/iterators/leftJoin.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { leftJoin } from '../../lib/iterators/leftJoin'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/leftJoin', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return empty source', () => { 8 | const source = []; 9 | const others = []; 10 | const iterator = leftJoin(source, others, x => x, x => x, x => x); 11 | 12 | expect(toArray(iterator)).to.be.deep.equal([]); 13 | }); 14 | }); 15 | 16 | describe('When called on some source', () => { 17 | it('Should return 3 joined items', () => { 18 | const source = [1, 2, 3]; 19 | const others = [2]; 20 | const iterator = leftJoin(source, others, x => x, x => x, (x, y) => ({ x, y })); 21 | 22 | expect(toArray(iterator)).to.be.deep.equal([ 23 | { x: 1, y: undefined }, 24 | { x: 2, y: 2 }, 25 | { x: 3, y: undefined }, 26 | ]); 27 | }); 28 | 29 | it('Should return 2 joined items', () => { 30 | const source = [1, 3]; 31 | const others = [2]; 32 | const iterator = leftJoin(source, others, x => x, x => x, (x, y) => ({ y, x: x * 10 })); 33 | 34 | expect(toArray(iterator)).to.be.deep.equal([ 35 | { x: 10, y: undefined }, 36 | { x: 30, y: undefined }, 37 | ]); 38 | }); 39 | 40 | it('Should return 4 joined items', () => { 41 | const source = ['a', 'b', 'c', 'd']; 42 | const others = ['add', 'eff', 'ccc']; 43 | const iterator = leftJoin(source, others, x => x, x => x[0], (x, y) => ({ x, y })); 44 | 45 | expect(toArray(iterator)).to.be.deep.equal([ 46 | { x: 'a', y: 'add' }, 47 | { x: 'b', y: undefined }, 48 | { x: 'c', y: 'ccc' }, 49 | { x: 'd', y: undefined }, 50 | ]); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/iterators/map.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { map } from '../../lib/iterators/map'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/map', () => { 6 | describe('When applying identity transformation', () => { 7 | it('Should return the same elements', () => { 8 | const source = [1, 2, 4, 8, 16]; 9 | const iterator = map(source, elem => elem); 10 | 11 | expect(toArray(iterator)).to.be.deep.equal([1, 2, 4, 8, 16]); 12 | }); 13 | it('Should return the same elements', () => { 14 | const source = [1.1, 2.2, 4.4, 8.8, 16.16]; 15 | const iterator = map(source, elem => elem); 16 | 17 | expect(toArray(iterator)).to.be.deep.equal([1.1, 2.2, 4.4, 8.8, 16.16]); 18 | }); 19 | }); 20 | 21 | describe('When applying linear transformation', () => { 22 | it('Should return the elements modified', () => { 23 | const source = [1, 2, 4, 8]; 24 | const iterator = map(source, elem => elem * 2 + 2); 25 | 26 | expect(toArray(iterator)).to.be.deep.equal([4, 6, 10, 18]); 27 | }); 28 | it('Should return the elements modified', () => { 29 | const source = ['a', 'b', 'c', 'd']; 30 | const iterator = map(source, elem => `${elem}a`); 31 | 32 | expect(toArray(iterator)).to.be.deep.equal(['aa', 'ba', 'ca', 'da']); 33 | }); 34 | }); 35 | 36 | describe('When called on empty source', () => { 37 | it('Should return empty source', () => { 38 | const source = []; 39 | const iterator = map(source, (elem, idx) => elem + idx + 1234); 40 | 41 | expect(toArray(iterator)).to.be.deep.equal([]); 42 | }); 43 | }); 44 | 45 | describe('When applying index transformation', () => { 46 | it('Should return the array indexes', () => { 47 | const source = [10, 1, 1, 2, 3]; 48 | const iterator = map(source, (_, idx) => idx); 49 | 50 | expect(toArray(iterator)).to.be.deep.equal([0, 1, 2, 3, 4]); 51 | }); 52 | }); 53 | 54 | describe('When applying index and value transformation', () => { 55 | it('Should return the index+value array', () => { 56 | const source = [2, 3, 4, 3, 2]; 57 | const iterator = map(source, (elem, idx) => idx + elem); 58 | 59 | expect(toArray(iterator)).to.be.deep.equal([2, 4, 6, 6, 6]); 60 | }); 61 | 62 | it('Should return the object.property + 2*index', () => { 63 | const source = [{ karma: 2 }, { karma: 4 }]; 64 | const iterator = map(source, (elem, idx) => elem.karma + 2 * idx); 65 | 66 | expect(toArray(iterator)).to.be.deep.equal([2, 6]); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/iterators/reverse.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { reverse } from '../../lib/iterators/reverse'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/reverse', () => { 6 | describe('When reversing empty array', () => { 7 | it('Should return empty array', () => { 8 | const source = []; 9 | const iterator = reverse(source); 10 | expect(toArray(iterator)).to.be.deep.equal([]); 11 | }); 12 | }); 13 | 14 | describe('When reversing one element', () => { 15 | it('Should return the same element', () => { 16 | const source = [8]; 17 | const iterator = reverse(source); 18 | expect(toArray(iterator)).to.be.deep.equal([8]); 19 | }); 20 | }); 21 | 22 | describe('When reversing multiple elements', () => { 23 | it('Should return elements in reverse order', () => { 24 | const source = [1, 3, 2]; 25 | const iterator = reverse(source); 26 | expect(toArray(iterator)).to.be.deep.equal([2, 3, 1]); 27 | }); 28 | it('Should return elements in reverse order', () => { 29 | const source = [1, 2]; 30 | const iterator = reverse(source); 31 | expect(toArray(iterator)).to.be.deep.equal([2, 1]); 32 | }); 33 | it('Should return elements in reverse order', () => { 34 | const source = [1, 32, 2, 4, 8, 16]; 35 | const iterator = reverse(source); 36 | expect(toArray(iterator)).to.be.deep.equal([16, 8, 4, 2, 32, 1]); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/iterators/shuffle.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { shuffle } from '../../lib/iterators/shuffle'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/shuffle', () => { 6 | describe('When shuffle an array', () => { 7 | it('Should contain the same 5 integers', () => { 8 | const source = [3, 4, 3, 1, 5]; 9 | const iterator = shuffle(source); 10 | 11 | expect(toArray(iterator).sort()).to.be.deep.equal(source.sort()); 12 | }); 13 | 14 | it('Should contain the same 3 floats', () => { 15 | const source = [3.1, 4.1, 3.1]; 16 | const iterator = shuffle(source); 17 | 18 | expect(toArray(iterator).sort()).to.be.deep.equal(source.sort()); 19 | }); 20 | 21 | it('Should contain the same 4 strings', () => { 22 | const source = ['asdf', 'sdffd', 'blackmagick']; 23 | const iterator = shuffle(source); 24 | 25 | expect(toArray(iterator).sort()).to.be.deep.equal(source.sort()); 26 | }); 27 | 28 | it('Should contain the same 4 objecs', () => { 29 | const source = [{ x: 1 }, { x: 10 }, { x: 11 }, { x: 2 }]; 30 | const iterator = shuffle(source); 31 | 32 | expect(toArray(iterator).sort((elem1, elem2) => elem1.x > elem2.x ? 1 : -1)) 33 | .to.be.deep.equal(source.sort((elem1, elem2) => elem1.x > elem2.x ? 1 : -1)); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/iterators/skip.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { skip } from '../../lib/iterators/skip'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/skip', () => { 6 | describe('When called on empty array', () => { 7 | it('Should return empty array', () => { 8 | const source = []; 9 | const iterator = skip(source, 1); 10 | 11 | expect(toArray(iterator)).to.be.deep.equal([]); 12 | }); 13 | }); 14 | 15 | describe('When skipping all elements', () => { 16 | it('Should return empty array', () => { 17 | const source = [1, 2, 4, 8, 16, 32, 64]; 18 | const iterator = skip(source, 100); 19 | 20 | expect(toArray(iterator)).to.be.deep.equal([]); 21 | }); 22 | }); 23 | 24 | describe('When skipping some elements', () => { 25 | it('Should return the remaining array', () => { 26 | const source = [1, 2, 4, 8, 16]; 27 | const iterator = skip(source, 2); 28 | 29 | expect(toArray(iterator)).to.be.deep.equal([4, 8, 16]); 30 | }); 31 | it('Should return the remaining array', () => { 32 | const source = [1, 2, 4, 8]; 33 | const iterator = skip(source, 0); 34 | 35 | expect(toArray(iterator)).to.be.deep.equal([1, 2, 4, 8]); 36 | }); 37 | it('Should return the remaining array', () => { 38 | const source = [1, 2, 4, 8, 16, 32]; 39 | const iterator = skip(source, 4); 40 | 41 | expect(toArray(iterator)).to.be.deep.equal([16, 32]); 42 | }); 43 | }); 44 | 45 | describe('When skipping negative count', () => { 46 | it('Should return first 3 elements', () => { 47 | const source = [1, 2, 3, 2, 1]; 48 | const iterator = skip(source, -2); 49 | 50 | expect(toArray(iterator)).to.be.deep.equal([1, 2, 3]); 51 | }); 52 | 53 | it('Should return first 2 elements', () => { 54 | const source = ['a', 'b', 'c', 'ddd', 'asdf']; 55 | const iterator = skip(source, -3); 56 | 57 | expect(toArray(iterator)).to.be.deep.equal(['a', 'b']); 58 | }); 59 | 60 | it('Should return empty source', () => { 61 | const source = ['a', 'b', 'c']; 62 | const iterator = skip(source, -3); 63 | 64 | expect(toArray(iterator)).to.be.deep.equal([]); 65 | }); 66 | 67 | it('Should return first element', () => { 68 | const source = [-10, 10]; 69 | const iterator = skip(source, -1); 70 | 71 | expect(toArray(iterator)).to.be.deep.equal([-10]); 72 | }); 73 | 74 | it('Should return first 6 elements', () => { 75 | const source = [0, -1, 2, -3, 4, 5, 6]; 76 | const iterator = skip(source, -1); 77 | 78 | expect(toArray(iterator)).to.be.deep.equal([0, -1, 2, -3, 4, 5]); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/iterators/skipWhile.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toArray } from '../../lib/reducers/toArray'; 3 | import { skipWhile } from '../../lib/iterators/skipWhile'; 4 | 5 | describe('iterators/skipWhile', () => { 6 | describe('When don\'t have searched elements', () => { 7 | it('Should return all elements', () => { 8 | const source = [1, 2]; 9 | const iterator = skipWhile(source, () => false); 10 | 11 | expect(toArray(iterator)).to.deep.equal([1, 2]); 12 | }); 13 | }); 14 | 15 | describe('When skip some elements', () => { 16 | it('Should skip first even elements', () => { 17 | const source = [0, 2, 4, 5, 6, 7, 8]; 18 | const iterator = skipWhile(source, e => e % 2 === 0); 19 | 20 | expect(toArray(iterator)).to.deep.equal([5, 6, 7, 8]); 21 | }); 22 | it('Should skip first positive elemts', () => { 23 | const source = [1, 2, 3, -2, 4, 5]; 24 | const iterator = skipWhile(source, e => e > 0); 25 | 26 | expect(toArray(iterator)).to.deep.equal([-2, 4, 5]); 27 | }); 28 | }); 29 | 30 | describe('When predicate is false for first element', () => { 31 | it('Should return all elements', () => { 32 | const source = [1, 2, 4, 6, 8]; 33 | const iterator = skipWhile(source, e => e % 2 === 0); 34 | 35 | expect(toArray(iterator)).to.deep.equal([1, 2, 4, 6, 8]); 36 | }); 37 | }); 38 | 39 | describe('When skip all elements', () => { 40 | it('Should return empty source', () => { 41 | const source = [1, 2, 3, 4, 5, 6]; 42 | const iterator = skipWhile(source, () => true); 43 | 44 | expect(toArray(iterator)).to.deep.equal([]); 45 | }); 46 | }); 47 | 48 | describe('When skip elements from empty source', () => { 49 | it('Should return empty source', () => { 50 | const source = []; 51 | const iterator = skipWhile(source, () => true); 52 | 53 | expect(toArray(iterator)).to.deep.equal([]); 54 | }); 55 | }); 56 | 57 | describe('When take elements by index', () => { 58 | it('Should return 3 elements', () => { 59 | const source = [1, 2, 3, 4, 5, 5, 5]; 60 | const iterator = skipWhile(source, (_, idx) => idx <= 3); 61 | 62 | expect(toArray(iterator)).to.deep.equal([5, 5, 5]); 63 | }); 64 | }); 65 | 66 | describe('When take elements by object property', () => { 67 | it('Should return 2 elements', () => { 68 | const source = [ 69 | { val: 1 }, 70 | { val: 10 }, 71 | { val: -1 }, 72 | { val: -2 }, 73 | ]; 74 | const iterator = skipWhile(source, e => e.val > 0); 75 | 76 | expect(toArray(iterator)).to.deep.equal([ 77 | { val: -1 }, 78 | { val: -2 }, 79 | ]); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/iterators/slice.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { iterator } from '../../lib/utils/iterator'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | import { slice } from '../../lib/iterators/slice'; 5 | 6 | describe('iterators/slice', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const source = slice([1, 2], 0, 1); 10 | 11 | expect(iterator(source)).not.equals(iterator(source)); 12 | }); 13 | }); 14 | 15 | describe('When called without start and end', () => { 16 | it('Should return the same source', () => { 17 | const source = [1, 2, 3, 1]; 18 | const it = slice(source); 19 | 20 | expect(toArray(it)).to.be.deep.equal([1, 2, 3, 1]); 21 | }); 22 | }); 23 | 24 | describe('When start is negative', () => { 25 | it('Should throw error', () => { 26 | const source = [4, 5, 3, 1, 2]; 27 | 28 | expect(() => toArray(slice(source, -10))).to.throw(Error); 29 | }); 30 | }); 31 | 32 | describe('When start is undefined and end is provided', () => { 33 | it('Should return array of 3 elements', () => { 34 | const source = [4, 5, 3, 2, 5, 4, 3]; 35 | const iter = slice(source, undefined, 4); 36 | 37 | expect(toArray(iter)).to.be.deep.equal([4, 5, 3, 2]); 38 | }); 39 | }); 40 | 41 | describe('When end is negative', () => { 42 | it('Should throw exception', () => { 43 | const source = []; 44 | 45 | expect(() => toArray(slice(source, 0, -1))).to.throw(Error); 46 | }); 47 | }); 48 | 49 | describe('When called with start only', () => { 50 | it('Should return array of 3 elements', () => { 51 | const source = [1, 3, 4, 2]; 52 | const iter = slice(source, 1); 53 | 54 | expect(toArray(iter)).to.be.deep.equal([3, 4, 2]); 55 | }); 56 | }); 57 | 58 | describe('When called with start and end', () => { 59 | it('Should return array of 2 elements', () => { 60 | const source = [1, 3, 4, 2, 1]; 61 | const iter = slice(source, 1, 3); 62 | 63 | expect(toArray(iter)).to.be.deep.equal([3, 4]); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/iterators/splice.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { iterator } from '../../lib/utils/iterator'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | import { splice } from '../../lib/iterators/splice'; 5 | 6 | describe('iterators/splice', () => { 7 | describe('when called multiple times', () => { 8 | it('Should return new iterator on each call', () => { 9 | const source = splice([1, 2], 0, 1, [3, 3, 3]); 10 | 11 | expect(iterator(source)).not.equals(iterator(source)); 12 | }); 13 | }); 14 | 15 | describe('When start is negative', () => { 16 | it('Should throw error', () => { 17 | const source = [4, 5, 3, 1, 2]; 18 | 19 | expect(() => toArray(splice(source, -10, 0, []))).to.throw(Error); 20 | }); 21 | }); 22 | 23 | describe('When delete count is negative', () => { 24 | it('Should throw exception', () => { 25 | const source = []; 26 | 27 | expect(() => toArray(splice(source, 0, -1, []))).to.throw(Error); 28 | }); 29 | }); 30 | 31 | describe('When called with zero delete count', () => { 32 | it('Should return array of 3 elements', () => { 33 | const source = [2]; 34 | const iter = splice(source, 0, 0, [4, 4]); 35 | 36 | expect(toArray(iter)).to.be.deep.equal([4, 4, 2]); 37 | }); 38 | 39 | it('Should return array of 5 elements', () => { 40 | const source = [2, 3]; 41 | const iter = splice(source, 1, 0, [4, 4]); 42 | 43 | expect(toArray(iter)).to.be.deep.equal([2, 4, 4, 3]); 44 | }); 45 | }); 46 | 47 | describe('When called with non-zero delete count', () => { 48 | it('Should return array of 2 elements', () => { 49 | const source = [1, 3, 4]; 50 | const iter = splice(source, 2, 1, [-1, -1, -1]); 51 | 52 | expect(toArray(iter)).to.be.deep.equal([1, 3, -1, -1, -1]); 53 | }); 54 | 55 | it('Should return array of 4 elements', () => { 56 | const source = [4, 5, 6, 3]; 57 | const iter = splice(source, 2, 10, [1, 2]); 58 | 59 | expect(toArray(iter)).to.be.deep.equal([4, 5, 1, 2]); 60 | }); 61 | 62 | it('Should return array of 5 elements', () => { 63 | const source = [1, 2, 3, 4, 5]; 64 | const iter = splice(source, 1, 1, [0]); 65 | 66 | expect(toArray(iter)).to.be.deep.equal([1, 0, 3, 4, 5]); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/iterators/take.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { take } from '../../lib/iterators/take'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('iterators/take', () => { 6 | describe('When take 0 elements', () => { 7 | it('Should return empty source', () => { 8 | const source = [1, 2]; 9 | const iterator = take(source, 0); 10 | 11 | expect(toArray(iterator)).to.deep.equal([]); 12 | }); 13 | }); 14 | 15 | describe('When take some elements', () => { 16 | it('Should return 4 elements from front', () => { 17 | const source = [1, 2, 3, 4, 5, 6]; 18 | const iterator = take(source, 4); 19 | 20 | expect(toArray(iterator)).to.deep.equal([1, 2, 3, 4]); 21 | }); 22 | it('Should return 2 elements from front', () => { 23 | const source = [1, 2, 3, 4, 5]; 24 | const iterator = take(source, 2); 25 | 26 | expect(toArray(iterator)).to.deep.equal([1, 2]); 27 | }); 28 | it('Should return 1 element from front', () => { 29 | const source = [1, 2, 3]; 30 | const iterator = take(source, 1); 31 | 32 | expect(toArray(iterator)).to.deep.equal([1]); 33 | }); 34 | }); 35 | 36 | describe('When take all elements', () => { 37 | it('Should return all elements', () => { 38 | const source = [1, 2, 3, 4, 5, 6]; 39 | const iterator = take(source, 6); 40 | 41 | expect(toArray(iterator)).to.deep.equal([1, 2, 3, 4, 5, 6]); 42 | }); 43 | }); 44 | 45 | describe('When take more than all elements', () => { 46 | it('Should return all elements', () => { 47 | const source = [1, 2, 3]; 48 | const iterator = take(source, 10); 49 | 50 | expect(toArray(iterator)).to.deep.equal([1, 2, 3]); 51 | }); 52 | }); 53 | 54 | describe('When take negative count', () => { 55 | it('Should return 3 elements from tail', () => { 56 | const source = [1, 2, 3, 4]; 57 | const iterator = take(source, -3); 58 | 59 | expect(toArray(iterator)).to.deep.equal([2, 3, 4]); 60 | }); 61 | it('Should return 5 elements from tail', () => { 62 | const source = [1, 2, 3, 4, 5, 6]; 63 | const iterator = take(source, -5); 64 | 65 | expect(toArray(iterator)).to.deep.equal([2, 3, 4, 5, 6]); 66 | }); 67 | it('Should return 1 element from tail', () => { 68 | const source = [1, 2, 3, 4, 5]; 69 | const iterator = take(source, -1); 70 | 71 | expect(toArray(iterator)).to.deep.equal([5]); 72 | }); 73 | }); 74 | 75 | describe('When take negative count of source size', () => { 76 | it('Should return the whole source', () => { 77 | const source = [1, 2, 3, 4, 5]; 78 | const iterator = take(source, -5); 79 | 80 | expect(toArray(iterator)).to.deep.equal([1, 2, 3, 4, 5]); 81 | }); 82 | }); 83 | 84 | describe('When take negative count of more than source size', () => { 85 | it('Should return the whole source', () => { 86 | const source = [1, 2, 3, 4]; 87 | const iterator = take(source, -100); 88 | 89 | expect(toArray(iterator)).to.deep.equal([1, 2, 3, 4]); 90 | }); 91 | }); 92 | 93 | describe('When take positive count from empty source', () => { 94 | it('Should return empty source', () => { 95 | const source = []; 96 | const iterator = take(source, 8); 97 | 98 | expect(toArray(iterator)).to.deep.equal([]); 99 | }); 100 | }); 101 | 102 | describe('When take negative count from empty source', () => { 103 | it('Should return empty source', () => { 104 | const source = []; 105 | const iterator = take(source, -16); 106 | 107 | expect(toArray(iterator)).to.deep.equal([]); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/iterators/takeWhile.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toArray } from '../../lib/reducers/toArray'; 3 | import { takeWhile } from '../../lib/iterators/takeWhile'; 4 | 5 | describe('iterators/takeWhile', () => { 6 | describe('When don\'t have searched elements', () => { 7 | it('Should return empty source', () => { 8 | const source = [1, 2]; 9 | const iterator = takeWhile(source, () => false); 10 | 11 | expect(toArray(iterator)).to.deep.equal([]); 12 | }); 13 | }); 14 | 15 | describe('When take some elements', () => { 16 | it('Should return first even elements', () => { 17 | const source = [0, 2, 4, 5, 6, 7, 8]; 18 | const iterator = takeWhile(source, e => e % 2 === 0); 19 | 20 | expect(toArray(iterator)).to.deep.equal([0, 2, 4]); 21 | }); 22 | it('Should return first positive elements', () => { 23 | const source = [1, 2, 3, -2, 4, 5]; 24 | const iterator = takeWhile(source, e => e > 0); 25 | 26 | expect(toArray(iterator)).to.deep.equal([1, 2, 3]); 27 | }); 28 | }); 29 | 30 | describe('When predicate is false for first element', () => { 31 | it('Should return empty source', () => { 32 | const source = [1, 2, 4, 6, 8]; 33 | const iterator = takeWhile(source, e => e % 2 === 0); 34 | 35 | expect(toArray(iterator)).to.deep.equal([]); 36 | }); 37 | }); 38 | 39 | describe('When take all elements', () => { 40 | it('Should return all elements', () => { 41 | const source = [1, 2, 3, 4, 5, 6]; 42 | const iterator = takeWhile(source, () => true); 43 | 44 | expect(toArray(iterator)).to.deep.equal([1, 2, 3, 4, 5, 6]); 45 | }); 46 | }); 47 | 48 | describe('When take elements from empty source', () => { 49 | it('Should return empty source', () => { 50 | const source = []; 51 | const iterator = takeWhile(source, () => true); 52 | 53 | expect(toArray(iterator)).to.deep.equal([]); 54 | }); 55 | }); 56 | 57 | describe('When take elements by index', () => { 58 | it('Should return 3 elements', () => { 59 | const source = [1, 2, 3, 4, 5, 5, 5]; 60 | const iterator = takeWhile(source, (_, idx) => idx < 3); 61 | 62 | expect(toArray(iterator)).to.deep.equal([1, 2, 3]); 63 | }); 64 | }); 65 | 66 | describe('When take elements by object property', () => { 67 | it('Should return 2 elements', () => { 68 | const source = [ 69 | { val: 1 }, 70 | { val: 10 }, 71 | { val: -1 }, 72 | { val: -2 }, 73 | ]; 74 | const iterator = takeWhile(source, e => e.val > 0); 75 | 76 | expect(toArray(iterator)).to.deep.equal([ 77 | { val: 1 }, 78 | { val: 10 }, 79 | ]); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/itiriri.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiriri } from '../lib'; 3 | import { SpyIterable } from './helpers/SpyIterable'; 4 | import { numbers as numberGenerator } from './helpers/generators'; 5 | import { toArray } from '../lib/reducers/toArray'; 6 | 7 | describe('Itiriri', () => { 8 | describe('When calling constructor', () => { 9 | it('Should return an Itiriri', () => { 10 | const source = []; 11 | const q = itiriri(source); 12 | 13 | const methods = [ 14 | 'entries', 'keys', 'values', 'forEach', 'concat', 'prepend', 'fill', 15 | 'toArray', 'toMap', 'toGroups', 'toSet', 'toString', 'filter', 'take', 'takeWhile', 16 | 'skip', 'skipWhile', 'slice', 'splice', 'join', 'leftJoin', 'rightJoin', 'groupJoin', 17 | 'sort', 'shuffle', 'reverse', 'every', 'some', 'includes', 18 | 'distinct', 'exclude', 'intersect', 'union', 'map', 'flat', 'groupBy', 19 | 'nth', 'indexOf', 'findIndex', 'lastIndexOf', 'findLastIndex', 'length', 20 | 'first', 'find', 'last', 'findLast', 'average', 'min', 21 | 'max', 'sum', 'reduce', 'reduceRight', 22 | ]; 23 | 24 | methods.forEach((method) => { 25 | expect(q).to.have.property(method); 26 | }); 27 | 28 | }); 29 | it('Iteration should be deferred', () => { 30 | const source = new SpyIterable([1, 2]); 31 | itiriri(source); 32 | 33 | expect(source.iterated).to.be.false; 34 | }); 35 | }); 36 | 37 | describe('When calling skip + take', () => { 38 | it('Should return 4 elemens', () => { 39 | const source = numberGenerator(1, 2); 40 | const q = itiriri(source).skip(2).take(4); 41 | 42 | expect(q.toArray()).to.be.deep.equal([5, 7, 9, 11]); 43 | }); 44 | 45 | it('Should return 1 element', () => { 46 | const source = numberGenerator(1, 2); 47 | const q = itiriri(source).skip(10).take(1); 48 | 49 | expect(q.nth(0)).to.be.equal(21); 50 | }); 51 | }); 52 | 53 | describe('When calling take + sort', () => { 54 | it('Should return 5 elements', () => { 55 | const source = numberGenerator(10, -1); 56 | const q = itiriri(source).take(5).sort(); 57 | 58 | expect(toArray(q)).to.be.deep.equal([6, 7, 8, 9, 10]); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/itiriri/cast.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiriri } from '../../lib'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | import { SpyIterable } from '../helpers/SpyIterable'; 5 | 6 | describe('Itiriri (cast)', () => { 7 | describe('When calling toArray', () => { 8 | it('Should return the array', () => { 9 | const source = [0, 4, 4, 0, 1]; 10 | const q = itiriri(source); 11 | 12 | expect(q.toArray()).to.deep.equal(source); 13 | }); 14 | 15 | it('Should return the array of transformed items', () => { 16 | const source = [1, 2, 3]; 17 | const q = itiriri(source); 18 | 19 | expect(q.toArray(e => e * 10)).to.deep.equal([10, 20, 30]); 20 | }); 21 | 22 | it('Should iterate once', () => { 23 | const source = new SpyIterable([]); 24 | itiriri(source).toArray(); 25 | 26 | expect(source.iteratedOnce).to.be.true; 27 | }); 28 | }); 29 | 30 | describe('When calling toMap', () => { 31 | it('Should throw error', () => { 32 | const source = [0, 4, 4, 0, 1]; 33 | const q = itiriri(source); 34 | 35 | expect(() => q.toMap(x => x)).to.throw(Error); 36 | }); 37 | 38 | it('Should return map of 3 elements', () => { 39 | const source = [0, 4, 5]; 40 | const q = itiriri(source).toMap(x => x, x => x); 41 | 42 | expect(toArray(q)).to.deep.equal([[0, 0], [4, 4], [5, 5]]); 43 | }); 44 | 45 | it('Should return map of 4 elements', () => { 46 | const source = [1, 4, 44, 11]; 47 | const q = itiriri(source).toMap(x => x, x => x % 10); 48 | 49 | expect(toArray(q)).to.deep.equal([[1, 1], [4, 4], [44, 4], [11, 1]]); 50 | }); 51 | 52 | it('Should iterate once', () => { 53 | const source = new SpyIterable([]); 54 | itiriri(source).toMap(x => x, x => x); 55 | 56 | expect(source.iteratedOnce).to.be.true; 57 | }); 58 | }); 59 | 60 | describe('When calling toSet', () => { 61 | it('Should return map of 4 elements', () => { 62 | const source = [0, 4, 5, 1]; 63 | const q = itiriri(source).toSet(); 64 | 65 | expect(toArray(q)).to.deep.equal([0, 4, 5, 1]); 66 | }); 67 | 68 | it('Should return map of 2 elements', () => { 69 | const source = [1, 4, 4, 1]; 70 | const q = itiriri(source).toSet(); 71 | 72 | expect(toArray(q)).to.deep.equal([1, 4]); 73 | }); 74 | 75 | it('Should iterate once', () => { 76 | const source = new SpyIterable([]); 77 | itiriri(source).toSet(); 78 | 79 | expect(source.iteratedOnce).to.be.true; 80 | }); 81 | }); 82 | 83 | describe('When calling toGroups', () => { 84 | it('Should return map of 2 groups', () => { 85 | const source = [0, 4, 5, 1]; 86 | const q = itiriri(source).toGroups(x => x % 2); 87 | 88 | expect(toArray(q)).to.deep.equal([[0, [0, 4]], [1, [5, 1]]]); 89 | }); 90 | 91 | it('Should return map of 3 groups', () => { 92 | const source = [ 93 | { val: 1, tag: 'a' }, 94 | { val: 11, tag: 'b' }, 95 | { val: 111, tag: 'a' }, 96 | { val: 1111, tag: 'c' }, 97 | ]; 98 | const q = itiriri(source).toGroups(x => x.tag); 99 | 100 | expect(toArray(q)).to.deep.equal([ 101 | ['a', [{ val: 1, tag: 'a' }, { val: 111, tag: 'a' }]], 102 | ['b', [{ val: 11, tag: 'b' }]], 103 | ['c', [{ val: 1111, tag: 'c' }]], 104 | ]); 105 | }); 106 | 107 | it('Should return map of 4 groups', () => { 108 | const source = [0, 1, 3, -1, -2]; 109 | const q = itiriri(source).toGroups((_, idx) => idx % 4); 110 | 111 | expect(toArray(q)).to.deep.equal([ 112 | [0, [0, -2]], 113 | [1, [1]], 114 | [2, [3]], 115 | [3, [-1]], 116 | ]); 117 | }); 118 | 119 | it('Should iterate once', () => { 120 | const source = new SpyIterable([]); 121 | itiriri(source).toMap(x => x); 122 | 123 | expect(source.iteratedOnce).to.be.true; 124 | }); 125 | }); 126 | 127 | describe('When calling toString', () => { 128 | it('Should return string of 3 elements', () => { 129 | const source = [4, 1, 2]; 130 | const q = itiriri(source); 131 | 132 | expect(q.toString()).to.be.equal('4,1,2'); 133 | }); 134 | 135 | it('Should return empty string', () => { 136 | const source = []; 137 | const q = itiriri(source); 138 | 139 | expect(q.toString()).to.be.equal(''); 140 | }); 141 | 142 | it('Should return string of 5 elements', () => { 143 | const source = [-1, null, 4, 1, 2]; 144 | const q = itiriri(source); 145 | 146 | expect(q.toString()).to.be.equal('-1,,4,1,2'); 147 | }); 148 | 149 | it('Should return string of 4 elements', () => { 150 | const source = [ 151 | { toString: () => 'a' }, 152 | { toString: () => 'aa' }, 153 | { toString: () => 'aaa' }, 154 | { toString: () => 'aaaa' }, 155 | ]; 156 | const q = itiriri(source); 157 | 158 | expect(q.toString()).to.be.equal('a,aa,aaa,aaaa'); 159 | }); 160 | 161 | it('Should iterate once', () => { 162 | const source = new SpyIterable([]); 163 | itiriri(source).toString(); 164 | 165 | expect(source.iteratedOnce).to.be.true; 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /test/itiriri/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { numbers as numberGenerator } from '../helpers/generators'; 3 | import { SpyIterable } from '../helpers/SpyIterable'; 4 | import { default as itiriri } from '../../lib'; 5 | 6 | describe('Itiriri (filter)', () => { 7 | describe('When calling filter', () => { 8 | it('Should be a deferred method', () => { 9 | const source = new SpyIterable(numberGenerator()); 10 | itiriri(source).filter(_ => true); 11 | 12 | expect(source.iterated).to.be.false; 13 | }); 14 | 15 | it('Should return array of 3 elements', () => { 16 | const source = [0, -4, 4, 30, -10, 10]; 17 | const q = itiriri(source).filter(x => x <= 0); 18 | 19 | expect(q.toArray()).to.be.deep.equal([0, -4, -10]); 20 | }); 21 | 22 | it('Should return array of 1 element', () => { 23 | const source = [0, -4, 4, 30, -10, 10]; 24 | const q = itiriri(source).filter((_, idx) => idx === 0); 25 | 26 | expect(q.toArray()).to.be.deep.equal([0]); 27 | }); 28 | 29 | it('Should return array of 1 object', () => { 30 | const source = [ 31 | { val: 10, tag: 'a' }, 32 | { val: 20, tag: 'b' }, 33 | { val: -10, tag: 'c' }, 34 | ]; 35 | const q = itiriri(source).filter(x => x.tag === 'a'); 36 | 37 | expect(q.toArray()).to.be.deep.equal([{ val: 10, tag: 'a' }]); 38 | }); 39 | 40 | it('Should be iterable multiple times', () => { 41 | const source = [1, 2, 3]; 42 | const q = itiriri(source).filter(x => x > 1); 43 | 44 | for (const _ of q) { } 45 | expect(q.toArray()).to.be.deep.equal([2, 3]); 46 | }); 47 | 48 | it('Should iterate once', () => { 49 | const source = new SpyIterable([]); 50 | itiriri(source).filter(_ => true).toArray(); 51 | 52 | expect(source.iteratedOnce).to.be.true; 53 | }); 54 | }); 55 | 56 | describe('When calling skip', () => { 57 | it('Should be a deferred method', () => { 58 | const source = new SpyIterable(numberGenerator()); 59 | itiriri(source).skip(1000); 60 | 61 | expect(source.iterated).to.be.false; 62 | }); 63 | 64 | it('Should return 5 elements', () => { 65 | const source = numberGenerator(); 66 | const q = itiriri(source).skip(2).take(5); 67 | 68 | expect(q.toArray()).to.be.deep.equal([2, 3, 4, 5, 6]); 69 | }); 70 | 71 | it('Should be iterable multiple times', () => { 72 | const source = [1, 2, 3]; 73 | const q = itiriri(source).skip(1); 74 | 75 | for (const _ of q) { } 76 | expect(q.toArray()).to.be.deep.equal([2, 3]); 77 | }); 78 | 79 | it('Should iterate once', () => { 80 | const source = new SpyIterable([]); 81 | itiriri(source).skip(0).toArray(); 82 | 83 | expect(source.iteratedOnce).to.be.true; 84 | }); 85 | }); 86 | 87 | describe('When calling slice', () => { 88 | it('Should be a deferred method', () => { 89 | const source = new SpyIterable(numberGenerator()); 90 | itiriri(source).slice(100, 2000); 91 | 92 | expect(source.iterated).to.be.false; 93 | }); 94 | 95 | it('Should return 3 elements', () => { 96 | const source = numberGenerator(1, 2); 97 | const q = itiriri(source).slice(4, 6); 98 | 99 | expect(q.toArray()).to.be.deep.equal([9, 11]); 100 | }); 101 | 102 | it('Should return empty source', () => { 103 | const source = numberGenerator(10, 2); 104 | const q = itiriri(source).slice(7, 6); 105 | 106 | expect(q.toArray()).to.be.deep.equal([]); 107 | }); 108 | 109 | it('Should return no elements', () => { 110 | const source = numberGenerator(10, 2); 111 | const q = itiriri(source).slice(0, 0); 112 | 113 | expect(q.toArray()).to.be.deep.equal([]); 114 | }); 115 | 116 | it('Should be iterable multiple times', () => { 117 | const source = [1, 2, 3, 4, 5]; 118 | const q = itiriri(source).slice(1, 3); 119 | 120 | for (const _ of q) { } 121 | expect(q.toArray()).to.be.deep.equal([2, 3]); 122 | }); 123 | 124 | it('Should iterate once', () => { 125 | const source = new SpyIterable([]); 126 | itiriri(source).slice().toArray(); 127 | 128 | expect(source.iteratedOnce).to.be.true; 129 | }); 130 | }); 131 | 132 | describe('When calling splice', () => { 133 | it('Should be a deferred method', () => { 134 | const source = new SpyIterable(numberGenerator()); 135 | itiriri(source).splice(100, 200, 0, 1, 2, 3, 4); 136 | 137 | expect(source.iterated).to.be.false; 138 | }); 139 | 140 | it('Should return 5 elemens', () => { 141 | const source = [1, 2, 3, 4, 5, 6, 7]; 142 | const q = itiriri(source).splice(1, 5, -1, -2, -3); 143 | 144 | expect(q.toArray()).to.be.deep.equal([1, -1, -2, -3, 7]); 145 | }); 146 | 147 | it('Should return 3 elemens', () => { 148 | const source = [1, 2, 3, 4, 5, 6, 7]; 149 | const q = itiriri(source).splice(0, 7, -1, -2, -3); 150 | 151 | expect(q.toArray()).to.be.deep.equal([-1, -2, -3]); 152 | }); 153 | 154 | it('Should be iterable multiple times', () => { 155 | const source = [1, 2, 3]; 156 | const q = itiriri(source).splice(1, 1, 42); 157 | 158 | for (const _ of q) { } 159 | expect(q.toArray()).to.be.deep.equal([1, 42, 3]); 160 | }); 161 | 162 | it('Should iterate once', () => { 163 | const source = new SpyIterable([]); 164 | itiriri(source).splice(0, 0).toArray(); 165 | 166 | expect(source.iteratedOnce).to.be.true; 167 | }); 168 | }); 169 | 170 | describe('When calling takeWhile', () => { 171 | it('Should be a deferred method', () => { 172 | const source = new SpyIterable(numberGenerator()); 173 | itiriri(source).takeWhile(() => true); 174 | 175 | expect(source.iterated).to.be.false; 176 | }); 177 | 178 | it('Should return all elements', () => { 179 | const source = [1, 2, 3, 4, 5]; 180 | const q = itiriri(source).takeWhile(() => true); 181 | 182 | expect(q.toArray()).to.be.deep.equal([1, 2, 3, 4, 5]); 183 | }); 184 | 185 | it('Should return no elements', () => { 186 | const source = [1, 2, 3, 4, 5]; 187 | const q = itiriri(source).takeWhile(() => false); 188 | 189 | expect(q.toArray()).to.be.deep.equal([]); 190 | }); 191 | 192 | it('Should return first elements that satisfy condition', () => { 193 | const source = [2, 4, 6, 7, 8, 3, 10]; 194 | const q = itiriri(source).takeWhile(e => e % 2 === 0); 195 | 196 | expect(q.toArray()).to.be.deep.equal([2, 4, 6]); 197 | }); 198 | 199 | it('Should be iterable multiple times', () => { 200 | const source = [1, 2, 3, 1]; 201 | const q = itiriri(source).takeWhile(x => x < 3); 202 | 203 | for (const _ of q) { } 204 | expect(q.toArray()).to.be.deep.equal([1, 2]); 205 | }); 206 | 207 | it('Should iterate once', () => { 208 | const source = new SpyIterable([]); 209 | itiriri(source).takeWhile(_ => true).toArray(); 210 | 211 | expect(source.iteratedOnce).to.be.true; 212 | }); 213 | }); 214 | 215 | describe('When calling skipWhile', () => { 216 | it('Should be a deferred method', () => { 217 | const source = new SpyIterable(numberGenerator()); 218 | itiriri(source).skipWhile(() => true); 219 | 220 | expect(source.iterated).to.be.false; 221 | }); 222 | 223 | it('Should skip all elements', () => { 224 | const source = [1, 2, 3, 4, 5]; 225 | const q = itiriri(source).skipWhile(() => true); 226 | 227 | expect(q.toArray()).to.be.deep.equal([]); 228 | }); 229 | 230 | it('Should return all elements', () => { 231 | const source = [1, 2, 3, 4, 5]; 232 | const q = itiriri(source).skipWhile(() => false); 233 | 234 | expect(q.toArray()).to.be.deep.equal([1, 2, 3, 4, 5]); 235 | }); 236 | 237 | it('Should skip first elements that satisfy condition', () => { 238 | const source = [2, 4, 6, 7, 8, 3, 10]; 239 | const q = itiriri(source).skipWhile(e => e % 2 === 0); 240 | 241 | expect(q.toArray()).to.be.deep.equal([7, 8, 3, 10]); 242 | }); 243 | 244 | it('Should be iterable multiple times', () => { 245 | const source = [1, 2, 3, 1]; 246 | const q = itiriri(source).skipWhile(x => x < 3); 247 | 248 | for (const _ of q) { } 249 | expect(q.toArray()).to.be.deep.equal([3, 1]); 250 | }); 251 | 252 | it('Should iterate once', () => { 253 | const source = new SpyIterable([]); 254 | itiriri(source).skipWhile(_ => true).toArray(); 255 | 256 | expect(source.iteratedOnce).to.be.true; 257 | }); 258 | }); 259 | }); 260 | -------------------------------------------------------------------------------- /test/itiriri/itiriri.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { numbers as numberGenerator } from '../helpers/generators'; 3 | import { SpyIterable } from '../helpers/SpyIterable'; 4 | import { default as itiriri } from '../../lib'; 5 | 6 | describe('Itiriri (itiriri)', () => { 7 | describe('When calling entries', () => { 8 | it('Should be a deferred method', () => { 9 | const source = new SpyIterable(numberGenerator()); 10 | itiriri(source).entries(); 11 | 12 | expect(source.iterated).to.be.false; 13 | }); 14 | 15 | it('Should return 4 key/value pairs', () => { 16 | const source = numberGenerator(0, 2); 17 | const q = itiriri(source).take(4).entries(); 18 | 19 | expect(q.toArray()).to.be.deep.equal([[0, 0], [1, 2], [2, 4], [3, 6]]); 20 | }); 21 | 22 | it('Should be iterable multiple times', () => { 23 | const source = [1, 2, 3]; 24 | const q = itiriri(source).entries(); 25 | 26 | for (const _ of q) { } 27 | expect(q.toArray()).to.be.deep.equal([[0, 1], [1, 2], [2, 3]]); 28 | }); 29 | 30 | it('Should iterate once', () => { 31 | const source = new SpyIterable([]); 32 | itiriri(source).entries().toArray(); 33 | 34 | expect(source.iteratedOnce).to.be.true; 35 | }); 36 | }); 37 | 38 | describe('When calling keys', () => { 39 | it('Should be a deferred method', () => { 40 | const source = new SpyIterable(numberGenerator()); 41 | itiriri(source).keys(); 42 | 43 | expect(source.iterated).to.be.false; 44 | }); 45 | 46 | it('Should return the keys', () => { 47 | const source = numberGenerator(); 48 | const q = itiriri(source).take(10).keys(); 49 | 50 | expect(q.toArray()).to.be.deep.equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 51 | }); 52 | 53 | it('Should be iterable multiple times', () => { 54 | const source = [1, 2, 3]; 55 | const q = itiriri(source).keys(); 56 | 57 | for (const _ of q) { } 58 | expect(q.toArray()).to.be.deep.equal([0, 1, 2]); 59 | }); 60 | 61 | it('Should iterate once', () => { 62 | const source = new SpyIterable([]); 63 | itiriri(source).keys().toArray(); 64 | 65 | expect(source.iteratedOnce).to.be.true; 66 | }); 67 | }); 68 | 69 | describe('When calling values', () => { 70 | it('Should be a deferred method', () => { 71 | const source = new SpyIterable(numberGenerator()); 72 | itiriri(source).values(); 73 | 74 | expect(source.iterated).to.be.false; 75 | }); 76 | 77 | it('Should return a new itiriri with same values', () => { 78 | const source = [1, 3, 4, 2, 2]; 79 | const q1 = itiriri(source); 80 | const q2 = q1.values(); 81 | 82 | expect(q2).to.not.be.equal(q1); 83 | expect(q2.toArray()).to.be.deep.equal(q1.toArray()); 84 | }); 85 | 86 | it('Should be iterable multiple times', () => { 87 | const source = [1, 2, 3]; 88 | const q = itiriri(source).values(); 89 | 90 | for (const _ of q) { } 91 | expect(q.toArray()).to.be.deep.equal(source); 92 | }); 93 | 94 | it('Should iterate once', () => { 95 | const source = new SpyIterable([]); 96 | itiriri(source).values().toArray(); 97 | 98 | expect(source.iteratedOnce).to.be.true; 99 | }); 100 | }); 101 | 102 | describe('When calling concat', () => { 103 | it('Should be a deferred method', () => { 104 | const source1 = new SpyIterable(numberGenerator()); 105 | const source2 = new SpyIterable(numberGenerator()); 106 | itiriri(source1).take(10).concat(itiriri(source2).take(5)); 107 | 108 | expect(source1.iterated).to.be.false; 109 | expect(source2.iterated).to.be.false; 110 | }); 111 | 112 | it('Should return 10 elements', () => { 113 | const source1 = numberGenerator(0, 2); 114 | const source2 = numberGenerator(); 115 | const q1 = itiriri(source1); 116 | const q2 = itiriri(source2).take(5).concat(q1.take(5)); 117 | 118 | expect(q2.toArray()).to.be.deep.equal([0, 1, 2, 3, 4, 0, 2, 4, 6, 8]); 119 | }); 120 | 121 | it('Should return 2 elements', () => { 122 | const source = numberGenerator(0, 2); 123 | const q = itiriri(source).take(1).concat(5); 124 | 125 | expect(q.toArray()).to.be.deep.equal([0, 5]); 126 | }); 127 | 128 | it('Should be iterable multiple times', () => { 129 | const source = [1, 2, 3]; 130 | const q = itiriri(source).concat(4); 131 | 132 | for (const _ of q) { } 133 | expect(q.toArray()).to.be.deep.equal([1, 2, 3, 4]); 134 | }); 135 | 136 | it('Should iterate once', () => { 137 | const source = new SpyIterable([]); 138 | itiriri(source).concat([]).toArray(); 139 | 140 | expect(source.iteratedOnce).to.be.true; 141 | }); 142 | }); 143 | 144 | describe('When calling prepend', () => { 145 | it('Should be a deferred method', () => { 146 | const source1 = new SpyIterable([1, 2, 3]); 147 | const source2 = new SpyIterable([]); 148 | itiriri(source1).prepend(source2); 149 | 150 | expect(source1.iterated).to.be.false; 151 | expect(source2.iterated).to.be.false; 152 | }); 153 | 154 | it('Should return 6 elemnts', () => { 155 | const source1 = numberGenerator(0, 10); 156 | const source2 = numberGenerator(100, 100); 157 | const q1 = itiriri(source1).skip(2).take(3); 158 | const q2 = itiriri(source2).prepend(q1.toArray()).take(6); 159 | 160 | expect(q2.toArray()).to.be.deep.equal([20, 30, 40, 100, 200, 300]); 161 | }); 162 | 163 | it('Should return 2 elements', () => { 164 | const source = [1]; 165 | const q = itiriri(source).prepend(2); 166 | 167 | expect(q.toArray()).to.be.deep.equal([2, 1]); 168 | }); 169 | 170 | it('Should be iterable multiple times', () => { 171 | const source = [1, 2, 3]; 172 | const q = itiriri(source).prepend(4); 173 | 174 | for (const _ of q) { } 175 | expect(q.toArray()).to.be.deep.equal([4, 1, 2, 3]); 176 | }); 177 | 178 | it('Should iterate once', () => { 179 | const source = new SpyIterable([]); 180 | itiriri(source).prepend([]).toArray(); 181 | 182 | expect(source.iteratedOnce).to.be.true; 183 | }); 184 | }); 185 | 186 | describe('When calling fill with positive indexes', () => { 187 | it('Should be a deferred method', () => { 188 | const source = new SpyIterable([1, 2, 3]); 189 | itiriri(source).fill(0, 1); 190 | 191 | expect(source.iterated).to.be.false; 192 | }); 193 | 194 | it('Should return 5 elements', () => { 195 | const source = numberGenerator(); 196 | const q = itiriri(source).fill(10, 1, 3).take(5); 197 | 198 | expect(q.toArray()).to.be.deep.equal([0, 10, 10, 3, 4]); 199 | }); 200 | 201 | it('Should return 4 elements', () => { 202 | const source = numberGenerator(); 203 | const q = itiriri(source).fill(11, 1, 1).take(4); 204 | 205 | expect(q.toArray()).to.be.deep.equal([0, 1, 2, 3]); 206 | }); 207 | 208 | it('Should return same elements', () => { 209 | const source = numberGenerator(); 210 | const q = itiriri(source).fill(100, 4, 1).take(10); 211 | 212 | expect(q.toArray()).to.be.deep.equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 213 | }); 214 | 215 | it('Should return 10 elements', () => { 216 | const source = numberGenerator(); 217 | const q = itiriri(source).fill(10, 2).take(10); 218 | 219 | expect(q.toArray()).to.be.deep.equal([0, 1, 10, 10, 10, 10, 10, 10, 10, 10]); 220 | }); 221 | 222 | it('Should return 4 elements', () => { 223 | const source = [1, -2, 3, -4]; 224 | const q = itiriri(source).fill(0); 225 | 226 | expect(q.toArray()).to.be.deep.equal([0, 0, 0, 0]); 227 | }); 228 | 229 | it('Should be iterable multiple times', () => { 230 | const source = [1, 2, 3]; 231 | const q = itiriri(source).fill(42); 232 | 233 | for (const _ of q) { } 234 | expect(q.toArray()).to.be.deep.equal([42, 42, 42]); 235 | }); 236 | 237 | it('Should iterate once', () => { 238 | const source = new SpyIterable([]); 239 | itiriri(source).fill(1).toArray(); 240 | 241 | expect(source.iteratedOnce).to.be.true; 242 | }); 243 | }); 244 | 245 | describe.skip('When calling fill with negative indexes', () => { 246 | it('Should return 5 elements', () => { 247 | const source = numberGenerator(); 248 | const q = itiriri(source).take(5).fill(10, -3, -1); 249 | 250 | expect(q.toArray()).to.be.deep.equal([0, 1, 10, 10, 4]); 251 | }); 252 | 253 | it('Should return 4 elements', () => { 254 | const source = numberGenerator(); 255 | const q = itiriri(source).take(4).fill(11, -1, -1); 256 | 257 | expect(q.toArray()).to.be.deep.equal([0, 1, 2, 3]); 258 | }); 259 | 260 | it('Should return same elements', () => { 261 | const source = numberGenerator(); 262 | const q = itiriri(source).take(10).fill(100, -1, -4); 263 | 264 | expect(q.toArray()).to.be.deep.equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 265 | }); 266 | 267 | it('Should return 4 elements', () => { 268 | const source = [-1, 2, -3, 4]; 269 | const q = itiriri(source).fill(0, -2); 270 | 271 | expect(q.toArray()).to.be.deep.equal([-1, 2, 0, 0]); 272 | }); 273 | }); 274 | 275 | describe.skip('When calling fill with mixed indexes', () => { 276 | it('Should return 5 elements', () => { 277 | const source = [4, 5, 1, 2, 3]; 278 | const q = itiriri(source).fill(0, 1, -1); 279 | 280 | expect(q.toArray()).to.be.deep.equal([4, 0, 0, 2, 3]); 281 | }); 282 | 283 | it('Should return 4 elements', () => { 284 | const source = [4, 1, 2, 3]; 285 | const q = itiriri(source).fill(0, -2, 3); 286 | 287 | expect(q.toArray()).to.be.deep.equal([4, 1, 0, 3]); 288 | }); 289 | 290 | it('Should return 6 elements', () => { 291 | const source = [4, 1, 5, 1, 2, 3]; 292 | const q = itiriri(source).fill(0, 4, -4); 293 | 294 | expect(q.toArray()).to.be.deep.equal([4, 1, 5, 1, 2, 3]); 295 | }); 296 | }); 297 | }); 298 | -------------------------------------------------------------------------------- /test/itiriri/join.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiriri } from '../../lib'; 3 | import { SpyIterable } from '../helpers/SpyIterable'; 4 | 5 | describe('Itiriri (join)', () => { 6 | describe('When calling join', () => { 7 | it('Should be a deferred method', () => { 8 | const source1 = new SpyIterable([]); 9 | const source2 = new SpyIterable([]); 10 | itiriri(source1).join(source2, x => x, x => x, x => x); 11 | 12 | expect(source1.iterated).to.be.false; 13 | expect(source2.iterated).to.be.false; 14 | }); 15 | 16 | it('Should return array of 2 elements', () => { 17 | const source1 = [0, 4, 5, 1]; 18 | const source2 = [-1, 4, 5, -1]; 19 | const q = itiriri(source1).join(source2, x => x, x => x, x => x); 20 | 21 | expect(q.toArray()).to.deep.equal([4, 5]); 22 | }); 23 | 24 | it('Should return array of 3 elements', () => { 25 | const source1 = [ 26 | { val: 1, tag: 'a' }, 27 | { val: 11, tag: 'b' }, 28 | { val: 111, tag: 'a' }, 29 | { val: 1111, tag: 'c' }, 30 | ]; 31 | const source2 = [ 32 | { val: 2, tag: 'a' }, 33 | { val: 2222, tag: 'c' }, 34 | ]; 35 | const q = itiriri(source1).join(source2, x => x.tag, x => x.tag, (e1, e2) => e1.val + e2.val); 36 | 37 | expect(q.toArray()).to.deep.equal([3, 113, 3333]); 38 | }); 39 | 40 | it('Should be iterable multiple times', () => { 41 | const source1 = [0, 4, 5, 1]; 42 | const source2 = [-1, 4, 5, -1]; 43 | const q = itiriri(source1).join(source2, x => x, x => x, x => x); 44 | 45 | for (const _ of q) { } 46 | expect(q.toArray()).to.deep.equal([4, 5]); 47 | }); 48 | 49 | it('Should iterate once', () => { 50 | const source = new SpyIterable([]); 51 | itiriri(source).join([], x => x, x => x, (a, b) => a + b).toArray(); 52 | 53 | expect(source.iteratedOnce).to.be.true; 54 | }); 55 | }); 56 | 57 | describe('When calling leftJoin', () => { 58 | it('Should be a deferred method', () => { 59 | const source1 = new SpyIterable([]); 60 | const source2 = new SpyIterable([]); 61 | itiriri(source1).leftJoin(source2, x => x, x => x, x => x); 62 | 63 | expect(source1.iterated).to.be.false; 64 | expect(source2.iterated).to.be.false; 65 | }); 66 | 67 | it('Should return array of 4 elements', () => { 68 | const source1 = [0, 4, 5, 1]; 69 | const source2 = [-1, 4, 5, -1]; 70 | const q = itiriri(source1).leftJoin(source2, x => x, x => x, (e1, e2) => ({ e1, e2 })); 71 | 72 | expect(q.toArray()).to.deep.equal([ 73 | { e1: 0, e2: undefined }, 74 | { e1: 4, e2: 4 }, 75 | { e1: 5, e2: 5 }, 76 | { e1: 1, e2: undefined }, 77 | ]); 78 | }); 79 | 80 | it('Should be iterable multiple times', () => { 81 | const source1 = [0, 4, 5, 1]; 82 | const source2 = [-1, 4, 5, -1]; 83 | const q = itiriri(source1).leftJoin(source2, x => x, x => x, (e1, e2) => ({ e1, e2 })); 84 | 85 | for (const _ of q) { } 86 | expect(q.toArray()).to.deep.equal([ 87 | { e1: 0, e2: undefined }, 88 | { e1: 4, e2: 4 }, 89 | { e1: 5, e2: 5 }, 90 | { e1: 1, e2: undefined }, 91 | ]); 92 | }); 93 | 94 | it('Should iterate once', () => { 95 | const source = new SpyIterable([]); 96 | itiriri(source).leftJoin([], x => x, x => x, (a, b) => a + b).toArray(); 97 | 98 | expect(source.iteratedOnce).to.be.true; 99 | }); 100 | }); 101 | 102 | describe('When calling rightJoin', () => { 103 | it('Should be a deferred method', () => { 104 | const source1 = new SpyIterable([]); 105 | const source2 = new SpyIterable([]); 106 | itiriri(source1).rightJoin(source2, x => x, x => x, x => x); 107 | 108 | expect(source1.iterated).to.be.false; 109 | expect(source2.iterated).to.be.false; 110 | }); 111 | 112 | it('Should return array of 4 elements', () => { 113 | const source1 = [0, 4, 5, 1]; 114 | const source2 = [-1, 4, 5, -2]; 115 | const q = itiriri(source1).rightJoin(source2, x => x, x => x, (e1, e2) => ({ e1, e2 })); 116 | 117 | expect(q.toArray()).to.deep.equal([ 118 | { e2: undefined, e1: -1 }, 119 | { e2: 4, e1: 4 }, 120 | { e2: 5, e1: 5 }, 121 | { e2: undefined, e1: -2 }, 122 | ]); 123 | }); 124 | 125 | it('Should return array of 4 elements', () => { 126 | const source1 = [ 127 | { category: 'Books', items: 10 }, 128 | { category: 'Cars', items: 20 }, 129 | { category: 'Guns', items: 20 }, 130 | { category: 'Phones', items: 10 }, 131 | ]; 132 | const source2 = [ 133 | { category: 'Books', profit: 100 }, 134 | { category: 'Cars', profit: 200000 }, 135 | { category: 'Guns', profit: 3000 }, 136 | { category: 'Rockets', profit: -100000 }, 137 | ]; 138 | const q = itiriri(source1).rightJoin( 139 | source2, 140 | x => x.category, 141 | x => x.category, 142 | (right, left) => 143 | `${left ? left.items : 'God knows'} ${left ? left.category : 'who'} ` + 144 | `produce ${right.profit}$ profit!`, 145 | ); 146 | 147 | expect(q.toArray()).to.be.deep.equal([ 148 | '10 Books produce 100$ profit!', 149 | '20 Cars produce 200000$ profit!', 150 | '20 Guns produce 3000$ profit!', 151 | 'God knows who produce -100000$ profit!', 152 | ]); 153 | }); 154 | 155 | it('Should be iterable multiple times', () => { 156 | const source1 = [0, 4, 5, 1]; 157 | const source2 = [-1, 4, 5, -2]; 158 | const q = itiriri(source1).rightJoin(source2, x => x, x => x, (e1, e2) => ({ e1, e2 })); 159 | 160 | for (const _ of q) { } 161 | expect(q.toArray()).to.deep.equal([ 162 | { e2: undefined, e1: -1 }, 163 | { e2: 4, e1: 4 }, 164 | { e2: 5, e1: 5 }, 165 | { e2: undefined, e1: -2 }, 166 | ]); 167 | }); 168 | 169 | it('Should iterate once', () => { 170 | const source = new SpyIterable([]); 171 | itiriri(source).rightJoin([], x => x, x => x, (a, b) => a + b).toArray(); 172 | 173 | expect(source.iteratedOnce).to.be.true; 174 | }); 175 | }); 176 | 177 | describe('When calling groupJoin', () => { 178 | it('Should be a deferred method', () => { 179 | const source1 = new SpyIterable([]); 180 | const source2 = new SpyIterable([]); 181 | itiriri(source1).groupJoin(source2, x => x, x => x, x => x); 182 | 183 | expect(source1.iterated).to.be.false; 184 | expect(source2.iterated).to.be.false; 185 | }); 186 | 187 | it('Should return array of 1 elements', () => { 188 | const source1 = [0, 4, 5, 1]; 189 | const source2 = [-1, 5, 5, 5, 1]; 190 | const q = itiriri(source1).groupJoin(source2, x => x, x => x, (e1, e2) => ({ e1, e2 })); 191 | 192 | expect(q.toArray()).to.deep.equal([ 193 | { e1: 0, e2: [] }, 194 | { e1: 4, e2: [] }, 195 | { e1: 5, e2: [5, 5, 5] }, 196 | { e1: 1, e2: [1] }, 197 | ]); 198 | }); 199 | 200 | it('Should be iterable multiple times', () => { 201 | const source1 = [0, 4, 5, 1]; 202 | const source2 = [-1, 5, 5, 5, 1]; 203 | const q = itiriri(source1).groupJoin(source2, x => x, x => x, (e1, e2) => ({ e1, e2 })); 204 | 205 | for (const _ of q) { } 206 | expect(q.toArray()).to.deep.equal([ 207 | { e1: 0, e2: [] }, 208 | { e1: 4, e2: [] }, 209 | { e1: 5, e2: [5, 5, 5] }, 210 | { e1: 1, e2: [1] }, 211 | ]); 212 | }); 213 | 214 | it('Should iterate once', () => { 215 | const source = new SpyIterable([]); 216 | itiriri(source).groupJoin([], x => x, x => x, (a, b) => a + b.length).toArray(); 217 | 218 | expect(source.iteratedOnce).to.be.true; 219 | }); 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /test/itiriri/permutation.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SpyIterable } from '../helpers/SpyIterable'; 3 | import { default as itiriri } from '../../lib'; 4 | import { toArray } from '../../lib/reducers/toArray'; 5 | 6 | describe('Itiriri (permutation)', () => { 7 | describe('When calling sort', () => { 8 | it('Should be a deferred method', () => { 9 | const source = new SpyIterable([1, 4, 2]); 10 | itiriri(source).sort(); 11 | 12 | expect(source.iterated).to.be.false; 13 | }); 14 | 15 | it('Should return array of 6 elements', () => { 16 | const source = [0, -4, 4, 30, 4, 10]; 17 | const q = itiriri(source).sort(); 18 | 19 | expect(q.toArray()).to.be.deep.equal([-4, 0, 4, 4, 10, 30]); 20 | }); 21 | 22 | it('Should return array of 3 objects', () => { 23 | const source = [ 24 | { val: 10, tag: 'a' }, 25 | { val: 20, tag: 'b' }, 26 | { val: -10, tag: 'c' }, 27 | ]; 28 | const q = itiriri(source).sort((e1, e2) => e1.val < e2.val ? -1 : 1); 29 | 30 | expect(q.toArray()).to.be.deep.equal([ 31 | { val: -10, tag: 'c' }, 32 | { val: 10, tag: 'a' }, 33 | { val: 20, tag: 'b' }, 34 | ]); 35 | }); 36 | 37 | it('Should be iterable multiple times', () => { 38 | const source = [2, 1, 3]; 39 | const q = itiriri(source).sort(); 40 | 41 | for (const _ of q) { } 42 | expect(q.toArray()).to.be.deep.equal([1, 2, 3]); 43 | }); 44 | 45 | it('Should iterate once', () => { 46 | const source = new SpyIterable([]); 47 | itiriri(source).sort().toArray(); 48 | 49 | expect(source.iteratedOnce).to.be.true; 50 | }); 51 | }); 52 | 53 | describe('When calling reverse', () => { 54 | it('Should be a deferred method', () => { 55 | const source = new SpyIterable([1, 4, 2]); 56 | itiriri(source).reverse(); 57 | 58 | expect(source.iterated).to.be.false; 59 | }); 60 | 61 | it('Should return array of 6 elements', () => { 62 | const source = [0, -4, 4, 30, -10, 10]; 63 | const q = itiriri(source).reverse(); 64 | 65 | expect(q.toArray()).to.be.deep.equal([10, -10, 30, 4, -4, 0]); 66 | }); 67 | 68 | it('Should return array of 3 objects', () => { 69 | const source = [ 70 | { val: 10, tag: 'a' }, 71 | { val: 20, tag: 'b' }, 72 | { val: -10, tag: 'c' }, 73 | ]; 74 | const q = itiriri(source).reverse(); 75 | 76 | expect(q.toArray()).to.be.deep.equal([ 77 | { val: -10, tag: 'c' }, 78 | { val: 20, tag: 'b' }, 79 | { val: 10, tag: 'a' }, 80 | ]); 81 | }); 82 | 83 | it('Should be iterable multiple times', () => { 84 | const source = [1, 2, 3]; 85 | const q = itiriri(source).reverse(); 86 | 87 | for (const _ of q) { } 88 | expect(q.toArray()).to.be.deep.equal([3, 2, 1]); 89 | }); 90 | 91 | it('Should iterate once', () => { 92 | const source = new SpyIterable([]); 93 | itiriri(source).reverse().toArray(); 94 | 95 | expect(source.iteratedOnce).to.be.true; 96 | }); 97 | }); 98 | 99 | describe('When calling shuffle', () => { 100 | it('Should be a deferred method', () => { 101 | const source = new SpyIterable([1, 4, 2]); 102 | itiriri(source).shuffle(); 103 | 104 | expect(source.iterated).to.be.false; 105 | }); 106 | 107 | it('Should return array of 6 elements', () => { 108 | const source = [0, -4, 4, 30, -10, 10]; 109 | const q = itiriri(source).shuffle(); 110 | const resultCheck = itiriri(toArray(q)).sort().toArray(); 111 | 112 | expect(resultCheck).to.be.deep.equal([-10, -4, 0, 4, 10, 30]); 113 | }); 114 | 115 | it('Should be iterable multiple times', () => { 116 | const source = [1, 2, 3]; 117 | const q = itiriri(source).shuffle(); 118 | 119 | for (const _ of q) { } 120 | expect(q.toArray().sort()).to.be.deep.equal([1, 2, 3]); 121 | }); 122 | 123 | it('Should iterate once', () => { 124 | const source = new SpyIterable([]); 125 | itiriri(source).shuffle().toArray(); 126 | 127 | expect(source.iteratedOnce).to.be.true; 128 | }); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/itiriri/predicate.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SpyIterable } from '../helpers/SpyIterable'; 3 | import { default as itiriri } from '../../lib'; 4 | 5 | describe('Itiriri (predicate)', () => { 6 | describe('When calling includes', () => { 7 | it('Should return true on array', () => { 8 | const source = [0, 4, 4, 30, 10, 10]; 9 | const q = itiriri(source); 10 | 11 | expect(q.includes(4)).to.be.true; 12 | }); 13 | 14 | it('Should return false on empty source', () => { 15 | const source: number[] = []; 16 | const q = itiriri(source); 17 | 18 | expect(q.includes(0)).to.be.false; 19 | }); 20 | 21 | it('Should return false when using fromIndex', () => { 22 | const source = [1, 2, 3, 4]; 23 | const q = itiriri(source); 24 | 25 | expect(q.includes(1, 1)).to.be.false; 26 | }); 27 | 28 | it('Should iterate once', () => { 29 | const source = new SpyIterable([]); 30 | itiriri(source).includes(1); 31 | 32 | expect(source.iteratedOnce).to.be.true; 33 | }); 34 | }); 35 | 36 | describe('When calling every', () => { 37 | it('Should return true on array', () => { 38 | const source = [0, 4, 4, 30, 10, 10]; 39 | const q = itiriri(source); 40 | 41 | expect(q.every(x => x >= 0)).to.be.true; 42 | }); 43 | 44 | it('Should return true on empty source', () => { 45 | const source = []; 46 | const q = itiriri(source); 47 | 48 | expect(q.every(x => x * 20 === 0)).to.be.true; 49 | }); 50 | 51 | it('Should return false on array of objects', () => { 52 | const source = [ 53 | { val: 10, tag: 'a' }, 54 | { val: 20, tag: 'b' }, 55 | { val: -10, tag: 'c' }, 56 | ]; 57 | const q = itiriri(source); 58 | 59 | expect(q.every(x => x.val <= 10)).to.be.false; 60 | }); 61 | 62 | it('Should return true on array of objects', () => { 63 | const source = [ 64 | { val: 10, tag: 'a' }, 65 | { val: 20, tag: 'b' }, 66 | { val: -10, tag: 'c' }, 67 | ]; 68 | const q = itiriri(source); 69 | 70 | expect(q.every((_, idx) => idx < 10)).to.be.true; 71 | }); 72 | 73 | it('Should iterate once', () => { 74 | const source = new SpyIterable([]); 75 | itiriri(source).every(_ => true); 76 | 77 | expect(source.iteratedOnce).to.be.true; 78 | }); 79 | }); 80 | 81 | describe('When calling some', () => { 82 | it('Should return true on array', () => { 83 | const source = [0, 4, 4, 30, 10, 10]; 84 | const q = itiriri(source); 85 | 86 | expect(q.some(x => x >= 30)).to.be.true; 87 | }); 88 | 89 | it('Should return true on empty source', () => { 90 | const source = []; 91 | const q = itiriri(source); 92 | 93 | expect(q.some(x => x * 20 === 0)).to.be.false; 94 | }); 95 | 96 | it('Should return false on array of objects', () => { 97 | const source = [ 98 | { val: 10, tag: 'a' }, 99 | { val: 20, tag: 'b' }, 100 | { val: -10, tag: 'c' }, 101 | ]; 102 | const q = itiriri(source); 103 | 104 | expect(q.some(x => x.val < -10)).to.be.false; 105 | }); 106 | 107 | it('Should return true on array of objects', () => { 108 | const source = [ 109 | { val: 10, tag: 'a' }, 110 | { val: 20, tag: 'b' }, 111 | { val: -10, tag: 'c' }, 112 | ]; 113 | const q = itiriri(source); 114 | 115 | expect(q.some((_, idx) => idx < 10)).to.be.true; 116 | }); 117 | 118 | it('Should iterate once', () => { 119 | const source = new SpyIterable([]); 120 | itiriri(source).some(_ => true); 121 | 122 | expect(source.iteratedOnce).to.be.true; 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /test/itiriri/set.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { default as itiriri } from '../../lib'; 3 | import { SpyIterable } from '../helpers/SpyIterable'; 4 | 5 | describe('Itiriri (set)', () => { 6 | describe('When calling distinct', () => { 7 | it('Should be a deferred method', () => { 8 | const source = new SpyIterable([0, 1, 2, 2, 1]); 9 | itiriri(source).distinct(x => x); 10 | 11 | expect(source.iterated).to.be.false; 12 | }); 13 | 14 | it('Should return array of 2 elements', () => { 15 | const source = [0, 4, 4, 0]; 16 | const q = itiriri(source).distinct(x => x); 17 | 18 | expect(q.toArray()).to.be.deep.equal([0, 4]); 19 | }); 20 | 21 | it('Should return array of 3 elements', () => { 22 | const source = [ 23 | { val: 1, tag: 'a' }, 24 | { val: 2, tag: 'b' }, 25 | { val: 3, tag: 'a' }, 26 | { val: 4, tag: 'a' }, 27 | { val: 5, tag: 'b' }, 28 | { val: 6, tag: 'c' }, 29 | ]; 30 | const q = itiriri(source).distinct(x => x.tag); 31 | 32 | expect(q.toArray()).to.be.deep.equal([ 33 | { val: 1, tag: 'a' }, 34 | { val: 2, tag: 'b' }, 35 | { val: 6, tag: 'c' }, 36 | ]); 37 | }); 38 | 39 | it('Should be iterable multiple times', () => { 40 | const source = [0, 4, 4, 0]; 41 | const q = itiriri(source).distinct(x => x); 42 | 43 | for (const _ of q) { } 44 | expect(q.toArray()).to.be.deep.equal([0, 4]); 45 | }); 46 | 47 | it('Should iterate once', () => { 48 | const source = new SpyIterable([]); 49 | itiriri(source).distinct().toArray(); 50 | 51 | expect(source.iteratedOnce).to.be.true; 52 | }); 53 | }); 54 | 55 | describe('When calling exclude', () => { 56 | it('Should be a deferred method', () => { 57 | const source1 = new SpyIterable([0, 1, 2, 2, 1]); 58 | const source2 = new SpyIterable([0, 1]); 59 | itiriri(source1).exclude(source2, x => x); 60 | 61 | expect(source1.iterated).to.be.false; 62 | expect(source2.iterated).to.be.false; 63 | }); 64 | 65 | it('Should return array of 2 elements', () => { 66 | const source1 = [0, 4, 4, 0]; 67 | const source2 = [0, 5]; 68 | const q = itiriri(source1).exclude(source2, x => x); 69 | 70 | expect(q.toArray()).to.be.deep.equal([4, 4]); 71 | }); 72 | 73 | it('Should return array of 3 elements', () => { 74 | const source1 = [ 75 | { val: 1, tag: 'a' }, 76 | { val: 2, tag: 'b' }, 77 | { val: 3, tag: 'a' }, 78 | { val: 4, tag: 'a' }, 79 | { val: 5, tag: 'b' }, 80 | { val: 6, tag: 'c' }, 81 | ]; 82 | const source2 = [{ val: 10, tag: 'a' }]; 83 | const q = itiriri(source1).exclude(source2, x => x.tag); 84 | 85 | expect(q.toArray()).to.be.deep.equal([ 86 | { val: 2, tag: 'b' }, 87 | { val: 5, tag: 'b' }, 88 | { val: 6, tag: 'c' }, 89 | ]); 90 | }); 91 | 92 | it('Should be iterable multiple times', () => { 93 | const source1 = [0, 4, 4, 0]; 94 | const source2 = [0, 5]; 95 | const q = itiriri(source1).exclude(source2, x => x); 96 | 97 | for (const _ of q) { } 98 | expect(q.toArray()).to.be.deep.equal([4, 4]); 99 | }); 100 | 101 | it('Should iterate once', () => { 102 | const source = new SpyIterable([]); 103 | itiriri(source).exclude([]).toArray(); 104 | 105 | expect(source.iteratedOnce).to.be.true; 106 | }); 107 | }); 108 | 109 | describe('When calling intersect', () => { 110 | it('Should be a deferred method', () => { 111 | const source1 = new SpyIterable([0, 1, 2, 2, 1]); 112 | const source2 = new SpyIterable([0, 1]); 113 | itiriri(source1).intersect(source2, x => x); 114 | 115 | expect(source1.iterated).to.be.false; 116 | expect(source2.iterated).to.be.false; 117 | }); 118 | 119 | it('Should return array of 2 elements', () => { 120 | const source1 = [0, 4, 4, 0, 1]; 121 | const source2 = [0, 5, 4]; 122 | const q = itiriri(source1).intersect(source2, x => x); 123 | 124 | expect(q.toArray()).to.be.deep.equal([0, 4]); 125 | }); 126 | 127 | it('Should return array of 1 elements', () => { 128 | const source1 = [ 129 | { val: 1, tag: 'a' }, 130 | { val: 2, tag: 'b' }, 131 | { val: 3, tag: 'a' }, 132 | { val: 4, tag: 'a' }, 133 | { val: 5, tag: 'b' }, 134 | { val: 6, tag: 'c' }, 135 | ]; 136 | const source2 = [{ val: 10, tag: 'a' }]; 137 | const q = itiriri(source1).intersect(source2, x => x.tag); 138 | 139 | expect(q.toArray()).to.be.deep.equal([{ val: 1, tag: 'a' }]); 140 | }); 141 | 142 | it('Should be iterable multiple times', () => { 143 | const source1 = [0, 4, 4, 0, 1]; 144 | const source2 = [0, 5, 4]; 145 | const q = itiriri(source1).intersect(source2, x => x); 146 | 147 | for (const _ of q) { } 148 | expect(q.toArray()).to.be.deep.equal([0, 4]); 149 | }); 150 | 151 | it('Should iterate once', () => { 152 | const source = new SpyIterable([]); 153 | itiriri(source).intersect([]).toArray(); 154 | 155 | expect(source.iteratedOnce).to.be.true; 156 | }); 157 | }); 158 | 159 | describe('When calling union', () => { 160 | it('Should be a deferred method', () => { 161 | const source1 = new SpyIterable([0, 1, 2, 2, 1]); 162 | const source2 = new SpyIterable([0, 1]); 163 | itiriri(source1).union(source2, x => x); 164 | 165 | expect(source1.iterated).to.be.false; 166 | expect(source2.iterated).to.be.false; 167 | }); 168 | 169 | it('Should return array of 4 elements', () => { 170 | const source1 = [0, 4, 4, 0, 1]; 171 | const source2 = [0, 5, 4]; 172 | const q = itiriri(source1).union(source2, x => x); 173 | 174 | expect(q.toArray()).to.be.deep.equal([0, 4, 1, 5]); 175 | }); 176 | 177 | it('Should return array of 3 elements', () => { 178 | const source1 = [ 179 | { val: 1, tag: 'a' }, 180 | { val: 11, tag: 'b' }, 181 | { val: 111, tag: 'a' }, 182 | { val: 1111, tag: 'c' }, 183 | ]; 184 | const source2 = [{ val: 10, tag: 'a' }]; 185 | const q = itiriri(source1).union(source2, x => x.tag); 186 | 187 | expect(q.toArray()).to.be.deep.equal([ 188 | { val: 1, tag: 'a' }, 189 | { val: 11, tag: 'b' }, 190 | { val: 1111, tag: 'c' }, 191 | ]); 192 | }); 193 | 194 | it('Should be iterable multiple times', () => { 195 | const source1 = [0, 4, 4, 0, 1]; 196 | const source2 = [0, 5, 4]; 197 | const q = itiriri(source1).union(source2, x => x); 198 | 199 | for (const _ of q) { } 200 | expect(q.toArray()).to.be.deep.equal([0, 4, 1, 5]); 201 | }); 202 | 203 | it('Should iterate once', () => { 204 | const source = new SpyIterable([]); 205 | itiriri(source).union([]).toArray(); 206 | 207 | expect(source.iteratedOnce).to.be.true; 208 | }); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /test/itiriri/transformation.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SpyIterable } from '../helpers/SpyIterable'; 3 | import { default as itiriri } from '../../lib'; 4 | 5 | describe('Itiriri (transformation)', () => { 6 | describe('When calling map', () => { 7 | it('Should be a deferred method', () => { 8 | const source = new SpyIterable([]); 9 | itiriri(source).map(x => x); 10 | 11 | expect(source.iterated).to.be.false; 12 | }); 13 | 14 | it('Should return array of 3 elements', () => { 15 | const source = [0, -4, 4]; 16 | const q = itiriri(source).map(x => x <= 0); 17 | 18 | expect(q.toArray()).to.be.deep.equal([true, true, false]); 19 | }); 20 | 21 | it('Should return array of 4 element', () => { 22 | const source = [0, -4, 4, 30]; 23 | const q = itiriri(source).map((elem, idx) => elem + idx); 24 | 25 | expect(q.toArray()).to.be.deep.equal([0, -3, 6, 33]); 26 | }); 27 | 28 | it('Should return array of 1 object', () => { 29 | const source = []; 30 | const q = itiriri(source).filter(x => x); 31 | 32 | expect(q.toArray()).to.be.deep.equal([]); 33 | }); 34 | 35 | it('Should be iterable multiple times', () => { 36 | const source = [1, 2, 3]; 37 | const q = itiriri(source).map(x => x * 10); 38 | 39 | for (const _ of q) { } 40 | expect(q.toArray()).to.be.deep.equal([10, 20, 30]); 41 | }); 42 | 43 | it('Should iterate once', () => { 44 | const source = new SpyIterable([]); 45 | itiriri(source).map(x => x).toArray(); 46 | 47 | expect(source.iteratedOnce).to.be.true; 48 | }); 49 | }); 50 | 51 | describe('When calling groupBy', () => { 52 | it('Should be a deferred method', () => { 53 | const source = new SpyIterable([]); 54 | itiriri(source).groupBy(x => x); 55 | 56 | expect(source.iterated).to.be.false; 57 | }); 58 | 59 | it('Should return array of 2 groups', () => { 60 | const source = [0, -4, 4, -1]; 61 | const q = itiriri(source).groupBy(x => x > 0); 62 | 63 | expect(q.toArray()).to.be.deep.equal([ 64 | [false, { source: [0, -4, -1] }], 65 | [true, { source: [4] }], 66 | ]); 67 | }); 68 | 69 | it('Should return array of 3 groups', () => { 70 | const source = [ 71 | { val: 1, tag: 'a' }, 72 | { val: 2, tag: 'b' }, 73 | { val: 3, tag: 'c' }, 74 | { val: 4, tag: 'd' }, 75 | { val: 5, tag: 'e' }, 76 | { val: 6, tag: 'f' }, 77 | ]; 78 | const q = itiriri(source).groupBy(x => x.val % 3); 79 | 80 | expect(q.toArray()).to.be.deep.equal([ 81 | [1, { source: [{ val: 1, tag: 'a' }, { val: 4, tag: 'd' }] }], 82 | [2, { source: [{ val: 2, tag: 'b' }, { val: 5, tag: 'e' }] }], 83 | [0, { source: [{ val: 3, tag: 'c' }, { val: 6, tag: 'f' }] }], 84 | ]); 85 | }); 86 | 87 | it('Should return array of 2 groups', () => { 88 | const source = [0, 4, 4, 1]; 89 | const q = itiriri(source).groupBy((_, idx) => idx % 2); 90 | 91 | expect(q.toArray()).to.be.deep.equal([ 92 | [0, { source: [0, 4] }], 93 | [1, { source: [4, 1] }], 94 | ]); 95 | }); 96 | 97 | it('Should be iterable multiple times', () => { 98 | const source = [0, 4, 4, 1]; 99 | const q = itiriri(source).groupBy((_, idx) => idx % 2); 100 | 101 | for (const _ of q) { } 102 | expect(q.toArray()).to.be.deep.equal([ 103 | [0, { source: [0, 4] }], 104 | [1, { source: [4, 1] }], 105 | ]); 106 | }); 107 | 108 | it('Should iterate once', () => { 109 | const source = new SpyIterable([]); 110 | itiriri(source).groupBy(x => x).toArray(); 111 | 112 | expect(source.iteratedOnce).to.be.true; 113 | }); 114 | }); 115 | 116 | describe('When calling flat', () => { 117 | it('Should be a deferred method', () => { 118 | const source = new SpyIterable([]); 119 | itiriri(source).flat(x => x); 120 | 121 | expect(source.iterated).to.be.false; 122 | }); 123 | 124 | it('Should return array of 5 elements', () => { 125 | const source = [[1, 2, 3], [4, 5]]; 126 | const q = itiriri(source).flat((elem, _) => { 127 | const res: number[] = []; 128 | 129 | elem.forEach((element) => { 130 | res.push(element); 131 | }); 132 | 133 | return res; 134 | }); 135 | 136 | expect(q.toArray()).to.be.deep.equal([1, 2, 3, 4, 5]); 137 | }); 138 | 139 | it('Should be iterable multiple times', () => { 140 | const source = [[1, 2], [3, 4]]; 141 | const q = itiriri(source).flat(x => x); 142 | 143 | for (const _ of q) { } 144 | expect(q.toArray()).to.be.deep.equal([1, 2, 3, 4]); 145 | }); 146 | 147 | it('Should iterate once', () => { 148 | const source = new SpyIterable([]); 149 | itiriri(source).flat(x => x).toArray(); 150 | 151 | expect(source.iteratedOnce).to.be.true; 152 | }); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/itiriri/value.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { numbers as numberGenerator } from '../helpers/generators'; 3 | import { default as itiriri } from '../../lib'; 4 | import { SpyIterable } from '../helpers/SpyIterable'; 5 | 6 | describe('Itiriri (value)', () => { 7 | describe('When calling nth', () => { 8 | describe('When positive index', () => { 9 | it('Should return first element', () => { 10 | const source = numberGenerator(); 11 | const q = itiriri(source); 12 | 13 | expect(q.nth(3)).to.be.equal(3); 14 | }); 15 | }); 16 | 17 | describe('When negative index', () => { 18 | it('Should return last element', () => { 19 | const source = numberGenerator(); 20 | const q = itiriri(source).take(100); 21 | 22 | expect(q.nth(-1)).to.be.equal(99); 23 | }); 24 | }); 25 | 26 | it('Should iterate once', () => { 27 | const source = new SpyIterable([]); 28 | itiriri(source).nth(0); 29 | 30 | expect(source.iteratedOnce).to.be.true; 31 | }); 32 | }); 33 | 34 | describe('When calling indexOf', () => { 35 | it('Should return first element index', () => { 36 | const source = numberGenerator(0, 3); 37 | const q = itiriri(source); 38 | 39 | expect(q.indexOf(0)).to.be.equal(0); 40 | }); 41 | 42 | it('Should return 5th element index', () => { 43 | const source = numberGenerator(0, 3); 44 | const q = itiriri(source); 45 | 46 | expect(q.indexOf(12)).to.be.equal(4); 47 | }); 48 | 49 | it('Should return 2nd element index', () => { 50 | const source = [2, 2, 2, 3, 4]; 51 | const q = itiriri(source); 52 | 53 | expect(q.indexOf(2, 1)).to.be.equal(1); 54 | }); 55 | 56 | it('Should iterate once', () => { 57 | const source = new SpyIterable([]); 58 | itiriri(source).indexOf(0); 59 | 60 | expect(source.iteratedOnce).to.be.true; 61 | }); 62 | }); 63 | 64 | describe('When calling lastIndexOf', () => { 65 | it('Should return first element index', () => { 66 | const source = [1, 3, 4, 33, 2, 4]; 67 | const q = itiriri(source); 68 | 69 | expect(q.lastIndexOf(1)).to.be.equal(0); 70 | }); 71 | 72 | it('Should return last element index', () => { 73 | const source = [1, 3, 4, 33, 2, 4]; 74 | const q = itiriri(source); 75 | 76 | expect(q.lastIndexOf(4)).to.be.equal(5); 77 | }); 78 | 79 | it('Should return 5th element index', () => { 80 | const source = [0, 1, 0, 0, 0, 2, 2, 2]; 81 | const q = itiriri(source); 82 | 83 | expect(q.lastIndexOf(0)).to.be.equal(4); 84 | }); 85 | 86 | it('Should return -1', () => { 87 | const source = [1, 1, 2, 3, 4, 1, 4]; 88 | const q = itiriri(source); 89 | 90 | expect(q.lastIndexOf(2, 3)).to.be.equal(-1); 91 | }); 92 | 93 | it('Should iterate once', () => { 94 | const source = new SpyIterable([]); 95 | itiriri(source).lastIndexOf(0); 96 | 97 | expect(source.iteratedOnce).to.be.true; 98 | }); 99 | }); 100 | 101 | describe('When calling findIndex', () => { 102 | it('Should return first element index', () => { 103 | const source = [1, 3, 4, 33, 2, 4]; 104 | const q = itiriri(source); 105 | 106 | expect(q.findIndex(x => x === 1)).to.be.equal(0); 107 | }); 108 | 109 | it('Should return last element index', () => { 110 | const source = [0, 1, 1, 1, 2, 44]; 111 | const q = itiriri(source); 112 | 113 | expect(q.findIndex(x => x - 10 > 30)).to.be.equal(5); 114 | }); 115 | 116 | it('Should return 5th element index', () => { 117 | const source = [0, 1, 0, 0, -1, 2, 2, 2]; 118 | const q = itiriri(source); 119 | 120 | expect(q.findIndex(x => x < 0)).to.be.equal(4); 121 | }); 122 | 123 | it('Should return -1', () => { 124 | const source = [0, 1, 0, 0, 1, 2, 2, 2]; 125 | const q = itiriri(source); 126 | 127 | expect(q.findIndex(x => x < 0)).to.be.equal(-1); 128 | }); 129 | 130 | it('Should iterate once', () => { 131 | const source = new SpyIterable([]); 132 | itiriri(source).findIndex(_ => true); 133 | 134 | expect(source.iteratedOnce).to.be.true; 135 | }); 136 | }); 137 | 138 | describe('When calling findLastIndex', () => { 139 | it('Should return first element index', () => { 140 | const source = [1, 3, 4, 33, 2, 4]; 141 | const q = itiriri(source); 142 | 143 | expect(q.findLastIndex(x => x === 1)).to.be.equal(0); 144 | }); 145 | 146 | it('Should return last element index', () => { 147 | const source = [100, 1, 1, 1, 2, 44]; 148 | const q = itiriri(source); 149 | 150 | expect(q.findLastIndex(x => x - 10 > 30)).to.be.equal(5); 151 | }); 152 | 153 | it('Should return 5th element index', () => { 154 | const source = [0, 1, 0, 0, -1, 2, 2, 2]; 155 | const q = itiriri(source); 156 | 157 | expect(q.findLastIndex(x => x < 0)).to.be.equal(4); 158 | }); 159 | 160 | it('Should return -1', () => { 161 | const source = [0, 1, 0, 0, 1, 2, 2, 2]; 162 | const q = itiriri(source); 163 | 164 | expect(q.findLastIndex(x => x < 0)).to.be.equal(-1); 165 | }); 166 | 167 | it('Should iterate once', () => { 168 | const source = new SpyIterable([]); 169 | itiriri(source).findLastIndex(_ => true); 170 | 171 | expect(source.iteratedOnce).to.be.true; 172 | }); 173 | }); 174 | 175 | describe('When calling length', () => { 176 | it('Should return 6', () => { 177 | const source = [1, 3, 4, 33, 2, 4]; 178 | const q = itiriri(source); 179 | 180 | expect(q.length()).to.be.equal(6); 181 | }); 182 | 183 | it('Should return 1', () => { 184 | const source = [1, 3, 4, 33, 2, 4]; 185 | const q = itiriri(source); 186 | 187 | expect(q.length(x => x > 10)).to.be.equal(1); 188 | }); 189 | 190 | it('Should return 3', () => { 191 | const source = [1, 3, 4, 33, 2, 4]; 192 | const q = itiriri(source); 193 | 194 | expect(q.length((_, idx) => idx > 2)).to.be.equal(3); 195 | }); 196 | 197 | it('Should iterate once', () => { 198 | const source = new SpyIterable([]); 199 | itiriri(source).length(); 200 | 201 | expect(source.iteratedOnce).to.be.true; 202 | }); 203 | }); 204 | 205 | describe('When calling first', () => { 206 | it('Should return 6', () => { 207 | const source = [6, 3, 4, 33, 2, 4]; 208 | const q = itiriri(source); 209 | 210 | expect(q.first()).to.be.equal(6); 211 | }); 212 | 213 | it('Should return undefined', () => { 214 | const source = []; 215 | const q = itiriri(source); 216 | 217 | expect(q.first()).to.be.undefined; 218 | }); 219 | 220 | it('Should return 3', () => { 221 | const source = numberGenerator(3, 0); 222 | const q = itiriri(source); 223 | 224 | expect(q.first()).to.be.equal(3); 225 | }); 226 | 227 | it('Should iterate once', () => { 228 | const source = new SpyIterable([]); 229 | itiriri(source).first(); 230 | 231 | expect(source.iteratedOnce).to.be.true; 232 | }); 233 | }); 234 | 235 | describe('When calling find', () => { 236 | it('Should return 33', () => { 237 | const source = [6, 3, 4, 33, 2, 4]; 238 | const q = itiriri(source); 239 | 240 | expect(q.find(x => x > 30)).to.be.equal(33); 241 | }); 242 | 243 | it('Should return undefined', () => { 244 | const source = [1, 2]; 245 | const q = itiriri(source); 246 | 247 | expect(q.find((elem, idx) => elem + idx === 0)).to.be.undefined; 248 | }); 249 | 250 | it('Should return first element', () => { 251 | const source = numberGenerator(3, 3); 252 | const q = itiriri(source).take(10); 253 | 254 | expect(q.find(x => x === 3)).to.be.equal(3); 255 | }); 256 | 257 | it('Should return 33', () => { 258 | const source = numberGenerator(3, 3); 259 | const q = itiriri(source); 260 | 261 | expect(q.find((_, idx) => idx === 10)).to.be.equal(33); 262 | }); 263 | 264 | it('Should iterate once', () => { 265 | const source = new SpyIterable([]); 266 | itiriri(source).find(_ => true); 267 | 268 | expect(source.iteratedOnce).to.be.true; 269 | }); 270 | }); 271 | 272 | describe('When calling last', () => { 273 | it('Should return 4', () => { 274 | const source = [6, 3, 4, 33, 2, 4]; 275 | const q = itiriri(source); 276 | 277 | expect(q.last()).to.be.equal(4); 278 | }); 279 | 280 | it('Should return undefined', () => { 281 | const source = []; 282 | const q = itiriri(source); 283 | 284 | expect(q.last()).to.be.undefined; 285 | }); 286 | 287 | it('Should iterate once', () => { 288 | const source = new SpyIterable([]); 289 | itiriri(source).last(); 290 | 291 | expect(source.iteratedOnce).to.be.true; 292 | }); 293 | }); 294 | 295 | describe('When calling findLast', () => { 296 | it('Should return 33', () => { 297 | const source = [6, 3, 4, 33, 2, 4]; 298 | const q = itiriri(source); 299 | 300 | expect(q.findLast(x => x > 30)).to.be.equal(33); 301 | }); 302 | 303 | it('Should return undefined', () => { 304 | const source = [1, 2]; 305 | const q = itiriri(source); 306 | 307 | expect(q.findLast((elem, idx) => elem + idx === 0)).to.be.undefined; 308 | }); 309 | 310 | it('Should return first element', () => { 311 | const source = [3, 4, 5, 5]; 312 | const q = itiriri(source).take(10); 313 | 314 | expect(q.findLast(x => x === 3)).to.be.equal(3); 315 | }); 316 | 317 | it('Should iterate once', () => { 318 | const source = new SpyIterable([]); 319 | itiriri(source).findLast(_ => true); 320 | 321 | expect(source.iteratedOnce).to.be.true; 322 | }); 323 | }); 324 | 325 | describe('When calling average', () => { 326 | it('Should return 33', () => { 327 | const source = [66, 0, 33]; 328 | const q = itiriri(source); 329 | 330 | expect(q.average()).to.be.equal(33); 331 | }); 332 | 333 | it('Should return undefined', () => { 334 | const source = []; 335 | const q = itiriri(source); 336 | 337 | expect(q.average()).to.be.undefined; 338 | }); 339 | 340 | it('Should return 10', () => { 341 | const source = [ 342 | { val: 10, tag: 'a' }, 343 | { val: 20, tag: 'b' }, 344 | { val: 0, tag: 'c' }, 345 | ]; 346 | const q = itiriri(source); 347 | 348 | expect(q.average(x => x.val)).to.be.equal(10); 349 | }); 350 | 351 | it('Should iterate once', () => { 352 | const source = new SpyIterable([]); 353 | itiriri(source).average(); 354 | 355 | expect(source.iteratedOnce).to.be.true; 356 | }); 357 | }); 358 | 359 | describe('When calling min', () => { 360 | it('Should return -1', () => { 361 | const source = [-1, 3, 4, 33, 2, 4]; 362 | const q = itiriri(source); 363 | 364 | expect(q.min()).to.be.equal(-1); 365 | }); 366 | 367 | it('Should return undefined', () => { 368 | const source = []; 369 | const q = itiriri(source); 370 | 371 | expect(q.min()).to.be.undefined; 372 | }); 373 | 374 | it('Should return first element', () => { 375 | const source = [ 376 | { val: -10, tag: 'a' }, 377 | { val: 20, tag: 'b' }, 378 | { val: 0, tag: 'c' }, 379 | ]; 380 | const q = itiriri(source); 381 | 382 | expect(q.min((e1, e2) => e1.val - e2.val)).to.be.equal(source[0]); 383 | }); 384 | 385 | it('Should iterate once', () => { 386 | const source = new SpyIterable([]); 387 | itiriri(source).min(); 388 | 389 | expect(source.iteratedOnce).to.be.true; 390 | }); 391 | }); 392 | 393 | describe('When calling max', () => { 394 | it('Should return 30', () => { 395 | const source = [-1, 3, 4, 30, 2, 4]; 396 | const q = itiriri(source); 397 | 398 | expect(q.max()).to.be.equal(30); 399 | }); 400 | 401 | it('Should return undefined', () => { 402 | const source = []; 403 | const q = itiriri(source); 404 | 405 | expect(q.max()).to.be.undefined; 406 | }); 407 | 408 | it('Should return first element', () => { 409 | const source = [ 410 | { val: 1010, tag: 'a' }, 411 | { val: 20, tag: 'b' }, 412 | { val: 0, tag: 'c' }, 413 | ]; 414 | const q = itiriri(source); 415 | 416 | expect(q.max((e1, e2) => e1.val - e2.val)).to.be.equal(source[0]); 417 | }); 418 | 419 | it('Should iterate once', () => { 420 | const source = new SpyIterable([]); 421 | itiriri(source).max(); 422 | 423 | expect(source.iteratedOnce).to.be.true; 424 | }); 425 | }); 426 | 427 | describe('When calling sum', () => { 428 | it('Should return 30', () => { 429 | const source = [0, -4, 4, 30, 10, -10]; 430 | const q = itiriri(source); 431 | 432 | expect(q.sum()).to.be.equal(30); 433 | }); 434 | 435 | it('Should return undefined', () => { 436 | const source = []; 437 | const q = itiriri(source); 438 | 439 | expect(q.sum()).to.be.undefined; 440 | }); 441 | 442 | it('Should iterate once', () => { 443 | const source = new SpyIterable([]); 444 | itiriri(source).sum(); 445 | 446 | expect(source.iteratedOnce).to.be.true; 447 | }); 448 | }); 449 | 450 | describe('When calling reduce', () => { 451 | it('Should return 0', () => { 452 | const source = [0, -4, 4, 30, 10, -10]; 453 | const q = itiriri(source); 454 | 455 | expect(q.reduce(() => 0, 0)).to.be.equal(0); 456 | }); 457 | 458 | it('Should throw exception', () => { 459 | const source: number[] = []; 460 | const q = itiriri(source); 461 | 462 | expect(() => q.reduce(() => 0)).to.throw(Error, 'Sequence contains no elements.'); 463 | }); 464 | 465 | it('Should return 20', () => { 466 | const source = [ 467 | { val: 10, tag: 'a' }, 468 | { val: 20, tag: 'b' }, 469 | { val: -10, tag: 'c' }, 470 | ]; 471 | const q = itiriri(source); 472 | 473 | expect(q.reduce((x, e) => x + e.val, 0)).to.be.equal(20); 474 | }); 475 | 476 | it('Should return abc', () => { 477 | const source = [ 478 | { val: 10, tag: 'a' }, 479 | { val: 20, tag: 'b' }, 480 | { val: -10, tag: 'c' }, 481 | ]; 482 | const q = itiriri(source); 483 | 484 | expect(q.reduce((x, e) => x + e.tag, '')).to.be.equal('abc'); 485 | }); 486 | 487 | it('Should iterate once', () => { 488 | const source = new SpyIterable([]); 489 | itiriri(source).reduce((a, b) => a + b, 0); 490 | 491 | expect(source.iteratedOnce).to.be.true; 492 | }); 493 | }); 494 | 495 | describe('When calling reduceRight', () => { 496 | it('Should return 0', () => { 497 | const source = [0, -4, 4, 30, 10, -10]; 498 | const q = itiriri(source); 499 | 500 | expect(q.reduceRight(() => 0, 0)).to.be.equal(0); 501 | }); 502 | 503 | it('Should throw exception', () => { 504 | const source = []; 505 | const q = itiriri(source); 506 | 507 | expect(() => q.reduceRight(() => 0)).to.throw(Error, 'Sequence contains no elements.'); 508 | }); 509 | 510 | it('Should return 20', () => { 511 | const source = [ 512 | { val: 10, tag: 'a' }, 513 | { val: 20, tag: 'b' }, 514 | { val: -10, tag: 'c' }, 515 | ]; 516 | const q = itiriri(source); 517 | 518 | expect(q.reduceRight((x, e) => x + e.val, 0)).to.be.equal(20); 519 | }); 520 | 521 | it('Should return cba', () => { 522 | const source = [ 523 | { val: 10, tag: 'a' }, 524 | { val: 20, tag: 'b' }, 525 | { val: -10, tag: 'c' }, 526 | ]; 527 | const q = itiriri(source); 528 | 529 | expect(q.reduceRight((x, e) => x + e.tag, '')).to.be.equal('cba'); 530 | }); 531 | 532 | it('Should iterate once', () => { 533 | const source = new SpyIterable([]); 534 | itiriri(source).reduceRight((a, b) => a + b, 0); 535 | 536 | expect(source.iteratedOnce).to.be.true; 537 | }); 538 | }); 539 | 540 | describe('When calling forEach', () => { 541 | it('Should return 4 transformed elements', () => { 542 | const source = numberGenerator(); 543 | const result: number[] = []; 544 | itiriri(source).take(4).forEach(elem => result.push(elem + 10)); 545 | 546 | expect(result).to.be.deep.equal([10, 11, 12, 13]); 547 | }); 548 | 549 | it('Should return 3 transformed elements', () => { 550 | const q = itiriri(numberGenerator(10, 10)); 551 | const result: number[] = []; 552 | q.take(3).forEach((elem, idx) => result.push(elem + idx)); 553 | 554 | expect(result).to.be.deep.equal([ 555 | 10, 21, 32, 556 | ]); 557 | }); 558 | 559 | it('Should iterate once', () => { 560 | const source = new SpyIterable([]); 561 | itiriri(source).forEach(() => { }); 562 | 563 | expect(source.iteratedOnce).to.be.true; 564 | }); 565 | }); 566 | }); 567 | -------------------------------------------------------------------------------- /test/reducers/average.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { average } from '../../lib/reducers/average'; 3 | 4 | describe('reducers/average', () => { 5 | describe('When calling on some integer array', () => { 6 | it('Should return the average of 4 elements', () => { 7 | const source = [0, 2, 2, 4]; 8 | 9 | expect(average(source)).to.be.equal(2); 10 | }); 11 | 12 | it('Should return the average of 1 element', () => { 13 | const source = [0]; 14 | 15 | expect(average(source)).to.be.equal(0); 16 | }); 17 | 18 | it('Should return the average of 2 elements', () => { 19 | const source = [0, -2]; 20 | 21 | expect(average(source)).to.be.equal(-1); 22 | }); 23 | 24 | it('Should return the average of 5 elements', () => { 25 | const source = [11.1, 2.2, 6.7, 14.5, 15.5]; 26 | 27 | expect(average(source)).to.be.equal(10.0); 28 | }); 29 | }); 30 | 31 | describe('When calling on empty source', () => { 32 | it('Should return undefined', () => { 33 | const source = []; 34 | 35 | expect(average(source)).to.be.undefined; 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/reducers/first.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { first } from '../../lib/reducers/first'; 3 | 4 | describe('reducers/first', () => { 5 | describe('When calling on some source', () => { 6 | it('Should return the first element', () => { 7 | const arr = [1, 2, 3]; 8 | 9 | expect(first(arr)).to.be.equal(1); 10 | }); 11 | 12 | it('Should return the first negative element', () => { 13 | const arr = [-1, 2, 3]; 14 | 15 | expect(first(arr)).to.be.equal(-1); 16 | }); 17 | 18 | it('Should return the first object element', () => { 19 | const arr = [{}, 1, 2, 3]; 20 | 21 | expect(first(arr)).to.be.equal(arr[0]); 22 | }); 23 | }); 24 | 25 | describe('When calling on empty source', () => { 26 | it('Should return undefined', () => { 27 | const arr = []; 28 | 29 | expect(first(arr)).to.be.undefined; 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/reducers/indexOf.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { indexOf } from '../../lib/reducers/indexOf'; 3 | 4 | describe('reducers/indexOf', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return -1', () => { 7 | const source = []; 8 | 9 | expect(indexOf(source, () => true)).to.be.equal(-1); 10 | }); 11 | }); 12 | 13 | describe('When called on some array', () => { 14 | it('Should return the index 3', () => { 15 | const source = [4, 5, 1, 20, 3]; 16 | 17 | expect(indexOf(source, elem => elem === 20)).to.be.equal(3); 18 | }); 19 | 20 | it('Should return the index 0', () => { 21 | const source = [1, 2, 1, 3, 4]; 22 | 23 | expect(indexOf(source, elem => elem === 1)).to.be.equal(0); 24 | }); 25 | 26 | it('Should return -1 if elements does not exist', () => { 27 | const source = ['a', 'b', 'z', 'aa', 'abc']; 28 | 29 | expect(indexOf(source, elem => elem === 'c')).to.be.equal(-1); 30 | }); 31 | 32 | it('Should return the index of last element', () => { 33 | const source = [5, 7, 1, 3]; 34 | 35 | expect(indexOf(source, elem => elem === 3)).to.be.equal(3); 36 | }); 37 | }); 38 | 39 | describe('When called with index depending predicate', () => { 40 | it('Should return first index', () => { 41 | const source = [1, 4, 3, 2]; 42 | 43 | expect(indexOf(source, (_, idx) => idx === 0)).to.be.equal(0); 44 | }); 45 | 46 | it('Should return last index', () => { 47 | const source = [1, 4, 3, 2, 5]; 48 | 49 | expect(indexOf(source, (_, idx) => { return idx * 2 === 4; })).to.be.equal(2); 50 | }); 51 | 52 | it('Should return the middle index', () => { 53 | const source = [1, 40, 3, 200, 1001]; 54 | 55 | expect(indexOf(source, (_, idx) => idx === 2)).to.be.equal(2); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/reducers/last.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { last } from '../../lib/reducers/last'; 3 | 4 | describe('reducers/last', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return undefined', () => { 7 | const source = []; 8 | 9 | expect(last(source)).to.be.undefined; 10 | }); 11 | }); 12 | 13 | describe('When called on array with one element', () => { 14 | it('Should return the element', () => { 15 | const source = [101]; 16 | 17 | expect(last(source)).to.be.equal(101); 18 | }); 19 | }); 20 | 21 | describe('When called on array with multiple elements', () => { 22 | it('Should return the last element (4th)', () => { 23 | const source = [1, 2, 3, 4]; 24 | 25 | expect(last(source)).to.be.equal(4); 26 | }); 27 | }); 28 | 29 | describe('When called on array with multiple elements', () => { 30 | it('Should return the last element (5th)', () => { 31 | const source = ['a', 'b', 'c', 'd', 'asdf']; 32 | 33 | expect(last(source)).to.be.equal('asdf'); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/reducers/lastIndexOf.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { lastIndexOf } from '../../lib/reducers/lastIndexOf'; 3 | 4 | describe('reducers/lastIndexOf', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return -1', () => { 7 | const source = []; 8 | 9 | expect(lastIndexOf(source, () => true)).to.be.equal(-1); 10 | }); 11 | }); 12 | 13 | describe('When called on some array', () => { 14 | it('Should return the index 3', () => { 15 | const source = [4, 5, 1, 20, 3]; 16 | 17 | expect(lastIndexOf(source, elem => elem === 20)).to.be.equal(3); 18 | }); 19 | 20 | it('Should return the index 2', () => { 21 | const source = [1, 2, 1, 3, 4]; 22 | 23 | expect(lastIndexOf(source, elem => elem === 1)).to.be.equal(2); 24 | }); 25 | 26 | it('Should return -1 if elements does not exist', () => { 27 | const source = ['a', 'b', 'z', 'aa', 'abc']; 28 | expect(lastIndexOf(source, elem => elem === 'c')).to.be.equal(-1); 29 | }); 30 | }); 31 | 32 | describe('When called with index depending predicate', () => { 33 | it('Should return first index', () => { 34 | const source = [1, 4, 3, 2]; 35 | 36 | expect(lastIndexOf(source, (_, idx) => idx === 0)).to.be.equal(0); 37 | }); 38 | 39 | it('Should return last index', () => { 40 | const source = [1, 4, 3, 2, 5]; 41 | 42 | expect(lastIndexOf(source, (_, idx) => { return idx * 2 === 4; })).to.be.equal(2); 43 | }); 44 | 45 | it('Should return last index for multiple matches', () => { 46 | const source = [1, 4, 3, 2, 5]; 47 | 48 | expect(lastIndexOf(source, (_, idx) => { return idx % 2 === 0; })).to.be.equal(4); 49 | }); 50 | 51 | it('Should return the middle index', () => { 52 | const source = [1, 40, 3, 200, 1001]; 53 | 54 | expect(lastIndexOf(source, (_, idx) => idx === 2)).to.be.equal(2); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/reducers/length.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { length } from '../../lib/reducers/length'; 3 | 4 | describe('reducers/length', () => { 5 | describe('When called on empty source', () => { 6 | it('Should return 0', () => { 7 | const arr = []; 8 | 9 | expect(length(arr)).to.equal(0); 10 | }); 11 | }); 12 | 13 | describe('When called on a non-empty source', () => { 14 | it('Should return length for 4 elements', () => { 15 | const arr = [0, 1, 2, 1]; 16 | 17 | expect(length(arr)).to.equal(4); 18 | }); 19 | it('Should return length for 1 element', () => { 20 | const arr = [0]; 21 | 22 | expect(length(arr)).to.equal(1); 23 | }); 24 | it('Should return length for 6 elements', () => { 25 | const arr = [0, 1, 1, 1, 0, 0]; 26 | 27 | expect(length(arr)).to.equal(6); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/reducers/max.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { max } from '../../lib/reducers/max'; 3 | 4 | describe('reducers/max', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return undefined', () => { 7 | const source = []; 8 | 9 | expect(max(source, (a, b) => a - b)).to.be.undefined; 10 | }); 11 | }); 12 | 13 | describe('When calle on some array', () => { 14 | it('Should return the first element', () => { 15 | const source = [4, 0, 1, 3]; 16 | 17 | expect(max(source, (a, b) => a - b)).to.be.equal(4); 18 | }); 19 | 20 | it('Should return the last element', () => { 21 | const source = [-1, 0, 1, 3]; 22 | 23 | expect(max(source, (a, b) => a - b)).to.be.equal(3); 24 | }); 25 | 26 | it('Should return the middle element', () => { 27 | const source = [-10, -2, -11, -13, -664]; 28 | 29 | expect(max(source, (a, b) => a - b)).to.be.equal(-2); 30 | }); 31 | 32 | it('Should return 10.99', () => { 33 | const source = [0.1, 9.9, 10.99, 10.1]; 34 | expect(max(source, (a, b) => a - b)).to.be.equal(10.99); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/reducers/min.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { min } from '../../lib/reducers/min'; 3 | 4 | describe('reducers/min', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return undefined', () => { 7 | const source = []; 8 | 9 | expect(min(source, (a, b) => a - b)).to.be.undefined; 10 | }); 11 | }); 12 | 13 | describe('When called on some array', () => { 14 | it('Should return the first element', () => { 15 | const source = [-1, 10, 17, 3]; 16 | 17 | expect(min(source, (a, b) => a - b)).to.be.equal(-1); 18 | }); 19 | 20 | it('Should return the last element', () => { 21 | const source = [-10, -2, -11, -13, -664]; 22 | 23 | expect(min(source, (a, b) => a - b)).to.be.equal(-664); 24 | }); 25 | 26 | it('Should return the middle element', () => { 27 | const source = [0.1, 9.9, -10.99, 10.1]; 28 | 29 | expect(min(source, (a, b) => a - b)).to.be.equal(-10.99); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/reducers/nth.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { nth } from '../../lib/reducers/nth'; 3 | 4 | describe('reducers/at', () => { 5 | describe('When accessing an element that exists', () => { 6 | it('Should return the element from first position', () => { 7 | const source = [1, 2, 3, 4, 5]; 8 | expect(nth(source, 0)).to.be.equal(1); 9 | }); 10 | 11 | it('Should return the element from a middle position', () => { 12 | const source = [1, 2, 3, 4, 5]; 13 | expect(nth(source, 2)).to.be.equal(3); 14 | }); 15 | 16 | it('Should return the element from last position', () => { 17 | const source = [1, 2, 3, 4, 5]; 18 | expect(nth(source, 4)).to.be.equal(5); 19 | }); 20 | }); 21 | 22 | describe('When accessing an element that does not exist', () => { 23 | it('Should return undefined (positive index)', () => { 24 | const source = [1, 2]; 25 | expect(nth(source, 100)).to.be.undefined; 26 | }); 27 | it('Should return undefined (negative index)', () => { 28 | const source = [1, 2, 6, 3]; 29 | expect(nth(source, -10)).to.be.undefined; 30 | }); 31 | }); 32 | 33 | describe('When accessing negative index element', () => { 34 | it('Should return last element', () => { 35 | const source = [32, 49, 3, 20]; 36 | expect(nth(source, -1)).to.be.equal(20); 37 | }); 38 | 39 | it('Should return a middle element', () => { 40 | const source = [1, 4, 1, 5]; 41 | expect(nth(source, -2)).to.be.equal(1); 42 | }); 43 | 44 | it('Should return first element', () => { 45 | const source = [4, 7, 2, 7]; 46 | expect(nth(source, -4)).to.be.equal(4); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/reducers/reduce.tests.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { reduce } from '../../lib/reducers/reduce'; 3 | 4 | describe('reducers/reduce', () => { 5 | describe('When called without initial value', () => { 6 | it('Should throw on empty source', () => { 7 | const source = []; 8 | 9 | expect(() => reduce(source, () => 1)) 10 | .to.throw(Error, 'Sequence contains no elements.'); 11 | }); 12 | 13 | it('Should calculate the sum of elements', () => { 14 | const source = [1, 2, 3]; 15 | 16 | expect(reduce(source, (a, b) => a + b)).to.equal(6); 17 | }); 18 | 19 | it('Should calculate the sum of squares', () => { 20 | const source = [2, 0, 4, 9]; 21 | 22 | // 2 + 0^2 + 4^2 + 9^2 = 201 23 | expect(reduce(source, (a, b) => a + b * b)).to.equal(99); 24 | }); 25 | 26 | it('Should calculate sum of indexes multiplied by 2', () => { 27 | const source = [5, 7, 1, 9]; 28 | 29 | // 5{initial element} + (1 + 1) * 2 + (2 + 1) * 2 + (3 + 1) * 2 = 23 30 | expect(reduce(source, (a, _, idx) => a + (idx + 1) * 2)).to.equal(23); 31 | }); 32 | }); 33 | 34 | describe('When called with initial value', () => { 35 | it('Should return the initial value on an empty source', () => { 36 | const source = []; 37 | 38 | expect(reduce(source, () => 1, 42)).to.equal(42); 39 | }); 40 | 41 | it('Should calculate the sum of elements plus 4', () => { 42 | const source = [1, 2, 3]; 43 | 44 | expect(reduce(source, (a, b) => a + b, 4)).to.equal(10); 45 | }); 46 | 47 | it('Should calculate the sum of squares plus 100', () => { 48 | const source = [2, 0, 4, 9]; 49 | 50 | // 100 + 2^2 + 0^2 + 4^2 + 9^2 = 201 51 | expect(reduce(source, (a, b) => a + b * b, 100)).to.equal(201); 52 | }); 53 | 54 | it('Should calculate sum of indexes multiplied by 2', () => { 55 | const source = [5, 7, 1, 9]; 56 | 57 | // 1{initial element} + (0 + 1) * 2 + (1 + 1) * 2 + (2 + 1) * 2 + (3 + 1) * 2 = 21 58 | expect(reduce(source, (a, _, idx) => a + (idx + 1) * 2, 1)).to.equal(21); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/reducers/sum.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { sum } from '../../lib/reducers/sum'; 3 | 4 | describe('reducers/sum', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return undefined', () => { 7 | const source = []; 8 | 9 | expect(sum(source)).to.be.undefined; 10 | }); 11 | }); 12 | 13 | describe('When called on some array', () => { 14 | it('Should return 3', () => { 15 | const source = [-1, 0, 1, 3]; 16 | 17 | expect(sum(source)).to.be.equal(3); 18 | }); 19 | 20 | it('Should return -40', () => { 21 | const source = [-10, -2, -11, -17]; 22 | 23 | expect(sum(source)).to.be.equal(-40); 24 | }); 25 | 26 | it('Should return 100.5', () => { 27 | const source = [10.1, 15.1, 15.1, 50.1, 10.1]; 28 | expect(sum(source)).to.be.equal(100.5); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/reducers/toArray.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toArray } from '../../lib/reducers/toArray'; 3 | 4 | describe('reducers/toArray', () => { 5 | describe('When called on empty source', () => { 6 | it('Should return empty source', () => { 7 | const source = []; 8 | 9 | expect(toArray(source)).to.be.deep.equal([]); 10 | }); 11 | }); 12 | 13 | describe('When called on some source', () => { 14 | it('Should return a copy', () => { 15 | const source = [1, 2, 3]; 16 | 17 | expect(toArray(source)).to.not.equal(source); 18 | }); 19 | 20 | it('Should return array of 3 elements', () => { 21 | const source = [1, 2, 3]; 22 | 23 | expect(toArray(source)).to.be.deep.equal([1, 2, 3]); 24 | }); 25 | 26 | it('Should return array of 4 elements', () => { 27 | const source = 'asdf'; 28 | 29 | expect(toArray(source)).to.be.deep.equal(['a', 's', 'd', 'f']); 30 | }); 31 | 32 | it('Should return array of 5 elements', () => { 33 | const source = new Set([5, 4, 3, 2, 1]); 34 | 35 | expect(toArray(source)).to.be.deep.equal([5, 4, 3, 2, 1]); 36 | }); 37 | 38 | it('Should return array from a generator', () => { 39 | const source = function* () { 40 | yield 1; 41 | yield 2; 42 | yield 3; 43 | }; 44 | 45 | expect(toArray(source())).to.be.deep.equal([1, 2, 3]); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/reducers/toMap.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toArray } from '../../lib/reducers/toArray'; 3 | import { toMap } from '../../lib/reducers/toMap'; 4 | 5 | describe('reducers/toMap', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return empty source', () => { 8 | const source = []; 9 | 10 | expect(toArray(toMap(source, x => x, x => x))).to.be.deep.equal([]); 11 | }); 12 | }); 13 | 14 | describe('When called on some source', () => { 15 | it('Should return set of 3 elements', () => { 16 | const source = [1, 2, 3]; 17 | 18 | expect(toArray(toMap(source, x => x, x => x))).to.be.deep.equal([ 19 | [1, 1], [2, 2], [3, 3], 20 | ]); 21 | }); 22 | 23 | it('Should return set of 4 elements', () => { 24 | const source = 'asdf'; 25 | 26 | expect(toArray(toMap(source, x => x, x => `${x}a`))) 27 | .to.be.deep.equal([ 28 | ['a', 'aa'], ['s', 'sa'], ['d', 'da'], ['f', 'fa']], 29 | ); 30 | }); 31 | 32 | it('Should return set of 5 elements', () => { 33 | const source = new Set([5, 4, 3, 2, 1]); 34 | 35 | expect(toArray(toMap(source, x => 2 * x, x => x - 1))) 36 | .to.be.deep.equal([ 37 | [10, 4], [8, 3], [6, 2], [4, 1], [2, 0]], 38 | ); 39 | }); 40 | 41 | it('Should throw an error for duplicate keys', () => { 42 | const source = 'asdfa'; 43 | 44 | expect(() => toArray(toMap(source, x => x, x => `${x}a`))) 45 | .to.throw(Error, 'Duplicate map entry key: a.'); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/reducers/toPartitions.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toArray } from '../../lib/reducers/toArray'; 3 | import { toGroups } from '../../lib/reducers/toGroups'; 4 | 5 | describe('reducers/toGroups', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return empty source', () => { 8 | const source = []; 9 | 10 | expect(toArray(toGroups(source, x => x, x => x))).to.be.deep.equal([]); 11 | }); 12 | }); 13 | 14 | describe('When called on some source', () => { 15 | it('Should return map of 3 elements', () => { 16 | const source = [1, 2, 3]; 17 | 18 | expect(toArray(toGroups(source, x => x, x => x))).to.be.deep.equal([ 19 | [1, [1]], 20 | [2, [2]], 21 | [3, [3]], 22 | ]); 23 | }); 24 | 25 | it('Should return map of 4 elements', () => { 26 | const source = 'asdfaa'; 27 | 28 | expect(toArray(toGroups(source, x => x, x => `${x}b`))).to.be.deep.equal([ 29 | ['a', ['ab', 'ab', 'ab']], 30 | ['s', ['sb']], 31 | ['d', ['db']], 32 | ['f', ['fb']], 33 | ]); 34 | }); 35 | 36 | it('Should return map of 5 elements', () => { 37 | const source = new Set([ 38 | { val: 1, a: 'a' }, 39 | { val: 2, a: 'b' }, 40 | { val: 2, a: 'c' }, 41 | ]); 42 | 43 | expect(toArray(toGroups(source, x => x.val, x => x.a))).to.be.deep.equal([ 44 | [1, ['a']], 45 | [2, ['b', 'c']], 46 | ]); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/reducers/toSet.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { toSet } from '../../lib/reducers/toSet'; 3 | import { toArray } from '../../lib/reducers/toArray'; 4 | 5 | describe('reducers/toSet', () => { 6 | describe('When called on empty source', () => { 7 | it('Should return empty source', () => { 8 | const source = []; 9 | 10 | expect(toArray(toSet(source))).to.be.deep.equal([]); 11 | }); 12 | }); 13 | 14 | describe('When called on some source', () => { 15 | it('Should return array of 3 elements', () => { 16 | const source = [1, 2, 3]; 17 | 18 | expect(toArray(toSet(source))).to.be.deep.equal([1, 2, 3]); 19 | }); 20 | 21 | it('Should return array of 4 elements', () => { 22 | const source = 'asdf'; 23 | 24 | expect(toArray(toSet(source))).to.be.deep.equal(['a', 's', 'd', 'f']); 25 | }); 26 | 27 | it('Should return array of 5 elements', () => { 28 | const source = new Set([5, 4, 3, 2, 1]); 29 | 30 | expect(toArray(toSet(source))).to.be.deep.equal([5, 4, 3, 2, 1]); 31 | }); 32 | 33 | it('Should return array of distinct elements', () => { 34 | const source = [1, 2, 3, 1, 1, 42]; 35 | 36 | expect(toArray(toSet(source))).to.be.deep.equal([1, 2, 3, 42]); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/utils/isIterable.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { isIterable } from '../../lib/utils/isIterable'; 3 | import { default as itiriri } from '../../lib'; 4 | 5 | describe('utils/isIterable', () => { 6 | describe('When called on array', () => { 7 | it('Should return true on empty array', () => { 8 | expect(isIterable([])).to.be.true; 9 | }); 10 | it('Should return true on non-empty array', () => { 11 | expect(isIterable([1, 'a'])).to.be.true; 12 | }); 13 | }); 14 | describe('When called on string', () => { 15 | it('Should return true on empty string', () => { 16 | expect(isIterable('')).to.be.true; 17 | }); 18 | it('Should return true on non-empty string', () => { 19 | expect(isIterable('asdf')).to.be.true; 20 | }); 21 | }); 22 | describe('When called on Itiriri', () => { 23 | it('Should return true', () => { 24 | expect(isIterable(itiriri([]))).to.be.true; 25 | }); 26 | }); 27 | describe('When called on Number', () => { 28 | it('Should return false', () => { 29 | expect(isIterable(4)).to.be.false; 30 | }); 31 | }); 32 | describe('When called on Object', () => { 33 | it('Should return false', () => { 34 | expect(isIterable({ a: 1, b: 'a' })).to.be.false; 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/utils/iterator.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { iterator } from '../../lib/utils/iterator'; 3 | 4 | describe('iterators/iterator', () => { 5 | describe('When called on empty array', () => { 6 | it('Should return an iterator', () => { 7 | const source = []; 8 | const it = iterator(source); 9 | 10 | expect(it).to.have.property('next'); 11 | }); 12 | 13 | it('Should return completed iterator', () => { 14 | const source = []; 15 | const it = iterator(source); 16 | 17 | expect(it.next()).to.have.property('done').that.is.true; 18 | }); 19 | 20 | it('Should return undefined next value', () => { 21 | const source = []; 22 | const it = iterator(source); 23 | 24 | expect(it.next()).to.have.property('value').that.is.undefined; 25 | }); 26 | }); 27 | 28 | describe('When called on array with one element', () => { 29 | it('Should iterate through element', () => { 30 | const source = [11]; 31 | const it = iterator(source); 32 | 33 | let current = it.next(); 34 | expect(current).to.have.property('value').that.is.equal(11); 35 | expect(current).to.have.property('done').that.is.false; 36 | 37 | current = it.next(); 38 | expect(current).to.have.property('value').that.is.undefined; 39 | expect(current).to.have.property('done').that.is.true; 40 | }); 41 | }); 42 | 43 | describe('When called on array with multiple elements', () => { 44 | it('Should iterate through elements', () => { 45 | const source = [4, 5, 10]; 46 | const it = iterator(source); 47 | 48 | let current = it.next(); 49 | expect(current).to.have.property('value').that.is.equal(4); 50 | expect(current).to.have.property('done').that.is.false; 51 | 52 | current = it.next(); 53 | expect(current).to.have.property('value').that.is.equal(5); 54 | expect(current).to.have.property('done').that.is.false; 55 | 56 | current = it.next(); 57 | expect(current).to.have.property('value').that.is.equal(10); 58 | expect(current).to.have.property('done').that.is.false; 59 | 60 | current = it.next(); 61 | expect(current).to.have.property('value').that.is.undefined; 62 | expect(current).to.have.property('done').that.is.true; 63 | }); 64 | }); 65 | 66 | describe('When called multiple times', () => { 67 | it('Should return new iterator on each call', () => { 68 | const source = [1, 2, 3]; 69 | 70 | const iterator1 = iterator(source); 71 | const iterator2 = iterator(source); 72 | 73 | expect(iterator1).not.equals(iterator2); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es6" 5 | ], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": false, 10 | "experimentalDecorators": true, 11 | "sourceMap": true, 12 | "rootDir": ".", 13 | "outDir": "build/compiled", 14 | "declaration": true, 15 | "alwaysStrict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "strictNullChecks": true, 19 | "removeComments": true 20 | }, 21 | "exclude": [ 22 | "build", 23 | "node_modules" 24 | ] 25 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-airbnb", 3 | "rules": { 4 | "no-increment-decrement": false, 5 | "function-name": false 6 | } 7 | } --------------------------------------------------------------------------------