├── index.js ├── src ├── NotDefinedAsyncTreeNode.js ├── Event.js ├── As.js ├── SimpleTreeNode.js ├── TreeNode.js ├── AsyncTree.js ├── AsyncObject.js └── AsyncTreeNode.js ├── test ├── BrokenEvent.js ├── BrokenAsyncObject.js ├── SyncMaxNum.js ├── InvokedEvent.js ├── EventOfAsyncObjectWithEvent.js ├── SafeAsyncObject.js ├── BrokenTreeNode.js ├── AsyncObjectWithoutError.js ├── AsyncObjectWithArgs.js ├── StrictEqualAssertion.js ├── DeepStrictEqualAssertion.js ├── AsyncMaxNum.js ├── AsyncObjectWithAssertedErrorInDefinedCall.js ├── AsyncObjectWithAssertedErrorInCallback.js ├── AsyncObjectWithAssertedResultInCallback.js ├── AsyncObjectWithAssertedSafeErrorInDefinedCall.js ├── AsyncObjectWithAssertedSafeResultInCallback.js ├── AsyncObjectWithAssertedSafeErrorAndResultInCallback.js ├── AsyncObjectWithAssertedSafeErrorInCallback.js └── test.js ├── .eslintrc.json ├── json └── ParsedJSON.js ├── wall ├── ExecutedLint.js ├── ExecutedTestCoverage.js ├── ExecutedTestCoverageCheck.js ├── ExecutedTestCoverageReport.js ├── custom-calls │ ├── executedTestCoverage.js │ ├── executedTestCoverageReport.js │ ├── executedLint.js │ └── executedTestCoverageCheck.js └── LoggedTotalCoverageByJsonSummary.js ├── fs └── ReadDataByPath.js ├── LICENSE ├── .gitignore ├── package.json ├── .travis.yml └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | AsyncObject: require('./src/AsyncObject'), 4 | Event: require('./src/Event'), 5 | as: require('./src/As') 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/NotDefinedAsyncTreeNode.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class NotDefinedAsyncTreeNode { 4 | constructor () {} 5 | } 6 | 7 | module.exports = NotDefinedAsyncTreeNode 8 | -------------------------------------------------------------------------------- /test/BrokenEvent.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Event = require('./../src/Event') 4 | 5 | class BrokenEvent extends Event { 6 | constructor () { 7 | super() 8 | } 9 | } 10 | 11 | module.exports = BrokenEvent 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "rules": { 4 | "no-useless-constructor": [ 5 | "off", "always" 6 | ], 7 | "standard/no-callback-literal": [ 8 | "off", "always" 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /test/BrokenAsyncObject.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class BrokenAsyncObject extends AsyncObject { 6 | constructor () { 7 | super() 8 | } 9 | } 10 | 11 | module.exports = BrokenAsyncObject 12 | -------------------------------------------------------------------------------- /src/Event.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class Event { 4 | constructor () {} 5 | 6 | // TO BE OVERRIDDEN 7 | 8 | body (...args) { 9 | throw new Error(`Method body must be overriden with arguments ${args} of the event/eventListener you call`) 10 | } 11 | } 12 | 13 | module.exports = Event 14 | -------------------------------------------------------------------------------- /json/ParsedJSON.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | 5 | // Represented result is json 6 | class ParsedJSON extends AsyncObject { 7 | constructor (string) { 8 | super(string) 9 | } 10 | 11 | syncCall () { 12 | return JSON.parse 13 | } 14 | } 15 | 16 | module.exports = ParsedJSON 17 | -------------------------------------------------------------------------------- /test/SyncMaxNum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class SyncMaxNum extends AsyncObject { 6 | constructor (a, b, c) { 7 | super(a, b, c) 8 | } 9 | 10 | syncCall () { 11 | return (a, b, c) => { 12 | return Math.max(a, b, c) 13 | } 14 | } 15 | } 16 | 17 | module.exports = SyncMaxNum 18 | -------------------------------------------------------------------------------- /test/InvokedEvent.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class InvokedEvent extends AsyncObject { 6 | constructor (event) { 7 | super(event) 8 | } 9 | 10 | syncCall () { 11 | return (event) => { 12 | event() 13 | return event 14 | } 15 | } 16 | } 17 | 18 | module.exports = InvokedEvent 19 | -------------------------------------------------------------------------------- /test/EventOfAsyncObjectWithEvent.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class EventOfAsyncObjectWithEvent extends AsyncObject { 6 | constructor (event) { 7 | super(event) 8 | } 9 | 10 | syncCall () { 11 | return (event) => { 12 | return event 13 | } 14 | } 15 | } 16 | 17 | module.exports = EventOfAsyncObjectWithEvent 18 | -------------------------------------------------------------------------------- /test/SafeAsyncObject.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class SafeAsyncObject extends AsyncObject { 6 | constructor () { 7 | super() 8 | } 9 | 10 | syncCall () { 11 | return () => { 12 | throw new Error('safe error') 13 | } 14 | } 15 | 16 | continueAfterFail () { 17 | return true 18 | } 19 | } 20 | 21 | module.exports = SafeAsyncObject 22 | -------------------------------------------------------------------------------- /wall/ExecutedLint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | const executedLint = require('./custom-calls/executedLint') 5 | 6 | // Represented result is process 7 | class ExecutedLint extends AsyncObject { 8 | constructor (process, ...files) { 9 | super(process, files) 10 | } 11 | 12 | asyncCall () { 13 | return executedLint 14 | } 15 | } 16 | 17 | module.exports = ExecutedLint 18 | -------------------------------------------------------------------------------- /test/BrokenTreeNode.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const TreeNode = require('./../src/TreeNode') 4 | 5 | class BrokenTreeNode extends TreeNode { 6 | /* 7 | field: simple argument (not AsyncObject, can be Event) 8 | parent: AsyncTreeNode or NotDefinedAsyncTree 9 | position: int 10 | */ 11 | constructor (field, parent, position) { 12 | super(field, parent, position) 13 | } 14 | } 15 | 16 | module.exports = BrokenTreeNode 17 | -------------------------------------------------------------------------------- /test/AsyncObjectWithoutError.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class AsyncObjectWithoutError extends AsyncObject { 6 | constructor () { 7 | super() 8 | } 9 | 10 | asyncCall () { 11 | return (callback) => { 12 | callback('value') 13 | } 14 | } 15 | 16 | callbackWithError () { 17 | return false 18 | } 19 | } 20 | 21 | module.exports = AsyncObjectWithoutError 22 | -------------------------------------------------------------------------------- /fs/ReadDataByPath.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | const fs = require('fs') 5 | 6 | // Represented result is buffer or string 7 | class ReadDataByPath extends AsyncObject { 8 | constructor (path, options) { 9 | super(path, options || { 10 | encoding: null, 11 | flag: 'r' 12 | }); 13 | } 14 | 15 | asyncCall () { 16 | return fs.readFile 17 | } 18 | } 19 | 20 | module.exports = ReadDataByPath 21 | -------------------------------------------------------------------------------- /test/AsyncObjectWithArgs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class AsyncObjectWithArgs extends AsyncObject { 6 | constructor (...args) { 7 | super(...args) 8 | } 9 | 10 | asyncCall () { 11 | return (...args) => { 12 | let callback = args[args.length - 1] 13 | return callback(null, ...args.slice(0, args.length - 1)) 14 | } 15 | } 16 | } 17 | 18 | module.exports = AsyncObjectWithArgs 19 | -------------------------------------------------------------------------------- /wall/ExecutedTestCoverage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | const executedTestCoverage = require('./custom-calls/executedTestCoverage') 5 | 6 | // Represented result is process 7 | class ExecutedTestCoverage extends AsyncObject { 8 | constructor (process, file) { 9 | super(process, file) 10 | } 11 | 12 | asyncCall () { 13 | return executedTestCoverage 14 | } 15 | } 16 | 17 | module.exports = ExecutedTestCoverage 18 | -------------------------------------------------------------------------------- /test/StrictEqualAssertion.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class StrictEqualAssertion extends AsyncObject { 7 | constructor (actual, expected) { 8 | super(actual, expected) 9 | } 10 | 11 | syncCall () { 12 | return (actual, expected) => { 13 | assert.strictEqual(actual, expected) 14 | return actual 15 | } 16 | } 17 | } 18 | 19 | module.exports = StrictEqualAssertion 20 | -------------------------------------------------------------------------------- /test/DeepStrictEqualAssertion.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class DeepStrictEqualAssertion extends AsyncObject { 7 | constructor (actual, expected) { 8 | super(actual, expected) 9 | } 10 | 11 | syncCall () { 12 | return (actual, expected) => { 13 | assert.deepStrictEqual(actual, expected) 14 | return actual 15 | } 16 | } 17 | } 18 | 19 | module.exports = DeepStrictEqualAssertion 20 | -------------------------------------------------------------------------------- /wall/ExecutedTestCoverageCheck.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | const executedTestCoverageCheck = require('./custom-calls/executedTestCoverageCheck') 5 | 6 | // Represented result is process 7 | class ExecutedTestCoverageCheck extends AsyncObject { 8 | constructor (process, options) { 9 | super(process, options) 10 | } 11 | 12 | asyncCall () { 13 | return executedTestCoverageCheck 14 | } 15 | } 16 | 17 | module.exports = ExecutedTestCoverageCheck 18 | -------------------------------------------------------------------------------- /src/As.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./AsyncObject') 4 | 5 | class As extends AsyncObject { 6 | constructor (key) { 7 | super(key) 8 | } 9 | 10 | syncCall () { 11 | return (key) => { 12 | let result = this.cache[key] 13 | if (result === undefined) { 14 | throw new Error(`There is no value that is cached with key ${key}`) 15 | } 16 | return result 17 | } 18 | } 19 | } 20 | 21 | module.exports = (key) => { 22 | return new As(key) 23 | } 24 | -------------------------------------------------------------------------------- /wall/ExecutedTestCoverageReport.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | const executedTestCoverageReport = require('./custom-calls/executedTestCoverageReport') 5 | 6 | // Represented result is process 7 | class ExecutedTestCoverageReport extends AsyncObject { 8 | constructor (process, format) { 9 | super(process, format || 'text') 10 | } 11 | 12 | asyncCall () { 13 | return executedTestCoverageReport 14 | } 15 | } 16 | 17 | module.exports = ExecutedTestCoverageReport 18 | -------------------------------------------------------------------------------- /test/AsyncMaxNum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | 5 | class AsyncMaxNum extends AsyncObject { 6 | constructor (a, b, c) { 7 | super(a, b, c) 8 | } 9 | 10 | /** 11 | It's not an operation with I/O, it's just silly example 12 | **/ 13 | asyncCall () { 14 | return (a, b, c, callback) => { 15 | callback(Math.max(a, b, c)) 16 | } 17 | } 18 | 19 | callbackWithError () { 20 | return false 21 | } 22 | } 23 | 24 | module.exports = AsyncMaxNum 25 | -------------------------------------------------------------------------------- /wall/custom-calls/executedTestCoverage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const spawn = require('child_process').spawn 4 | 5 | module.exports = (process, testFile, callback) => { 6 | const testCoverage = spawn('./node_modules/.bin/nyc', ['node', testFile], { 7 | stdio: [process.stdin, process.stdout, process.stderr] 8 | }) 9 | testCoverage.on('close', (code) => { 10 | if (code === 0) { 11 | callback(null, process) 12 | } else { 13 | callback(new Error(`test coverage failed with code ${code}`)) 14 | } 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /wall/custom-calls/executedTestCoverageReport.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const spawn = require('child_process').spawn 4 | 5 | module.exports = (process, format, callback) => { 6 | const testCoverage = spawn('./node_modules/.bin/nyc', ['report', '--reporter', format], { 7 | stdio: [process.stdin, process.stdout, process.stderr] 8 | }) 9 | testCoverage.on('close', (code) => { 10 | if (code === 0) { 11 | callback(null, process) 12 | } else { 13 | callback(new Error(`test coverage report failed with code ${code}`)) 14 | } 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/SimpleTreeNode.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const TreeNode = require('./TreeNode') 4 | 5 | class SimpleTreeNode extends TreeNode { 6 | /* 7 | field: simple argument (not AsyncObject, can be Event) 8 | parent: AsyncTreeNode or NotDefinedAsyncTree 9 | position: int 10 | */ 11 | constructor (field, parent, position) { 12 | super(field, parent, position) 13 | } 14 | 15 | // PUBLIC 16 | 17 | call () { 18 | super.callParent(this.field) 19 | } 20 | 21 | isLeaf () { 22 | return true 23 | } 24 | } 25 | 26 | module.exports = SimpleTreeNode 27 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedErrorInDefinedCall.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedErrorInDefinedCall extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return () => { 13 | throw new Error('async error') 14 | } 15 | } 16 | 17 | onError (error) { 18 | assert.deepStrictEqual(error, new Error('async error')) 19 | } 20 | } 21 | 22 | module.exports = AsyncObjectWithAssertedErrorInDefinedCall 23 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedErrorInCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedErrorInCallback extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return (callback) => { 13 | return callback(new Error('async error')) 14 | } 15 | } 16 | 17 | onError (error) { 18 | assert.deepStrictEqual(error, new Error('async error')) 19 | } 20 | } 21 | 22 | module.exports = AsyncObjectWithAssertedErrorInCallback 23 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedResultInCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedResultInCallback extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return (callback) => { 13 | return callback(null, 'result') 14 | } 15 | } 16 | 17 | onResult (result) { 18 | assert.strictEqual(result, 'result') 19 | return result 20 | } 21 | } 22 | 23 | module.exports = AsyncObjectWithAssertedResultInCallback 24 | -------------------------------------------------------------------------------- /wall/custom-calls/executedLint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const spawn = require('child_process').spawn 4 | 5 | module.exports = (process, files, callback) => { 6 | const lint = spawn('./node_modules/.bin/eslint', files, { 7 | stdio: [process.stdin, process.stdout, process.stderr] 8 | }) 9 | lint.on('close', (code) => { 10 | if (code === 0) { 11 | console.log('\x1b[32m%s\x1b[0m', `lint has executed successfully: everything is ok for ${files.join(', ')}`) 12 | callback(null, process) 13 | } else { 14 | callback(new Error(`lint failed for ${files.join(', ')} with code ${code}`)) 15 | } 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedSafeErrorInDefinedCall.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedSafeErrorInDefinedCall extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return () => { 13 | throw new Error('safe async error') 14 | } 15 | } 16 | 17 | onErrorAndResult (error) { 18 | assert.deepStrictEqual(error, new Error('safe async error')) 19 | } 20 | 21 | continueAfterFail () { 22 | return true 23 | } 24 | } 25 | 26 | module.exports = AsyncObjectWithAssertedSafeErrorInDefinedCall 27 | -------------------------------------------------------------------------------- /wall/custom-calls/executedTestCoverageCheck.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const spawn = require('child_process').spawn 4 | 5 | module.exports = (process, { lines, functions, branches }, callback) => { 6 | const testCoverage = spawn( 7 | './node_modules/.bin/nyc', 8 | ['check-coverage', '--lines', lines || 90, '--functions', functions || 90, '--branches', branches || 90], 9 | { stdio: [process.stdin, process.stdout, process.stderr] } 10 | ) 11 | testCoverage.on('close', (code) => { 12 | if (code === 0) { 13 | callback(null, process) 14 | } else { 15 | callback(new Error(`test coverage configuration failed with code ${code}`)) 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /wall/LoggedTotalCoverageByJsonSummary.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { AsyncObject } = require('./../index') 4 | 5 | // Represented result is logged string 6 | class LoggedTotalCoverageByJsonSummary extends AsyncObject { 7 | constructor (json, formula) { 8 | super(json, formula) 9 | } 10 | 11 | syncCall () { 12 | return (json, formula) => { 13 | let totalValue = formula(json.total.lines.pct, json.total.statements.pct, json.total.functions.pct, json.total.branches.pct) 14 | let report = `Total coverage: ${totalValue}%` 15 | console.log(report) 16 | return report 17 | } 18 | } 19 | } 20 | 21 | module.exports = LoggedTotalCoverageByJsonSummary 22 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedSafeResultInCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedSafeResultInCallback extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return (callback) => { 13 | return callback(null, 'result') 14 | } 15 | } 16 | 17 | onErrorAndResult (error, result) { 18 | assert.strictEqual(error, null) 19 | assert.strictEqual(result, 'result') 20 | return result 21 | } 22 | 23 | continueAfterFail () { 24 | return true 25 | } 26 | } 27 | 28 | module.exports = AsyncObjectWithAssertedSafeResultInCallback 29 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedSafeErrorAndResultInCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedSafeErrorAndResultInCallback extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return (callback) => { 13 | return callback(null, 'result') 14 | } 15 | } 16 | 17 | onErrorAndResult (error, result) { 18 | assert.strictEqual(super.onErrorAndResult(error, result), 'result') 19 | return result 20 | } 21 | 22 | continueAfterFail () { 23 | return true 24 | } 25 | } 26 | 27 | module.exports = AsyncObjectWithAssertedSafeErrorAndResultInCallback 28 | -------------------------------------------------------------------------------- /test/AsyncObjectWithAssertedSafeErrorInCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncObject = require('./../src/AsyncObject') 4 | const assert = require('assert') 5 | 6 | class AsyncObjectWithAssertedSafeErrorInCallback extends AsyncObject { 7 | constructor () { 8 | super() 9 | } 10 | 11 | asyncCall () { 12 | return (callback) => { 13 | return callback(new Error('safe async error'), 'result') 14 | } 15 | } 16 | 17 | onErrorAndResult (error, result) { 18 | assert.deepStrictEqual(error, new Error('safe async error')) 19 | assert.strictEqual(result, 'result') 20 | return error 21 | } 22 | 23 | continueAfterFail () { 24 | return true 25 | } 26 | } 27 | 28 | module.exports = AsyncObjectWithAssertedSafeErrorInCallback 29 | -------------------------------------------------------------------------------- /src/TreeNode.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* abstract class */ 4 | 5 | class TreeNode { 6 | /* 7 | field: just some value (argument), also can be Event 8 | parent: AsyncTreeNode 9 | position: int 10 | */ 11 | constructor (field, parent, position) { 12 | this.field = field 13 | this.parent = parent 14 | this.position = position 15 | } 16 | 17 | // TO BE OVERRIDEN 18 | 19 | call (result) { 20 | result = result || '' 21 | throw new Error(`call must be overridden and insert result ${result} into parent node`) 22 | } 23 | 24 | isLeaf () { 25 | throw new Error('isLeaf must be overridden') 26 | } 27 | 28 | // NOT ALLOWED TO BE OVERRIDDEN 29 | 30 | callParent (result) { 31 | this.parent.insertArgumentResult(this.position, result) 32 | if (this.parent.readyToBeInvoked()) { 33 | this.parent.call() 34 | } 35 | } 36 | } 37 | 38 | module.exports = TreeNode 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Guseyn Ismayylov 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .DS_Store 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cuties/cutie", 3 | "version": "1.7.6", 4 | "description": "Cutie is a library with beautiful abstractions and primitives that make your asynchronous code in Node simple and declarative.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "node test/test", 11 | "build": "node build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Guseyn/cutie.git" 16 | }, 17 | "keywords": [ 18 | "cutie", 19 | "async", 20 | "object", 21 | "tree", 22 | "abstractions", 23 | "oop", 24 | "desing", 25 | "guseyn" 26 | ], 27 | "author": "guseyn", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/Guseyn/cutie/issues" 31 | }, 32 | "homepage": "https://github.com/Guseyn/cutie#readme", 33 | "devDependencies": { 34 | "eslint": "^5.13.0", 35 | "eslint-config-standard": "^12.0.0", 36 | "eslint-plugin-import": "^2.16.0", 37 | "eslint-plugin-node": "^8.0.1", 38 | "eslint-plugin-promise": "^4.0.1", 39 | "eslint-plugin-standard": "^4.0.0", 40 | "nyc": "^14.1.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AsyncTree.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SimpleTreeNode = require('./SimpleTreeNode') 4 | const AsyncTreeNode = require('./AsyncTreeNode') 5 | const NotDefinedAsyncTreeNode = require('./NotDefinedAsyncTreeNode') 6 | 7 | class AsyncTree { 8 | /* 9 | rootField: AsyncObject 10 | */ 11 | constructor (rootField) { 12 | this.rootField = rootField 13 | this.nodes = [] 14 | } 15 | 16 | // PUBLIC 17 | 18 | create () { 19 | this.createAsyncTreeNode(this.rootField, new NotDefinedAsyncTreeNode(), 0) 20 | return this 21 | } 22 | 23 | call () { 24 | let leaves = this.nodes.filter(node => { 25 | return node.isLeaf() 26 | }) 27 | leaves.forEach(leaf => { 28 | leaf.call() 29 | }) 30 | } 31 | 32 | // PRIVATE 33 | 34 | createChildNodes (field, parent) { 35 | field.iterateArgs((argAsField, index, isAsyncObject, isEvent) => { 36 | if (isAsyncObject) { 37 | this.createAsyncTreeNode(argAsField, parent, index) 38 | } else if (isEvent) { 39 | this.createSimpleTreeNode((...eventArgs) => { 40 | argAsField.body(...eventArgs) 41 | }, parent, index) 42 | } else { 43 | this.createSimpleTreeNode(argAsField, parent, index) 44 | } 45 | }) 46 | } 47 | 48 | createAsyncTreeNode (field, parent, index) { 49 | let asyncTreeNode = new AsyncTreeNode(field, parent, index) 50 | this.nodes.push(asyncTreeNode) 51 | this.createChildNodes(field, asyncTreeNode) 52 | } 53 | 54 | createSimpleTreeNode (field, parent, index) { 55 | let treeNode = new SimpleTreeNode(field, parent, index) 56 | this.nodes.push(treeNode) 57 | } 58 | } 59 | 60 | module.exports = AsyncTree 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | install: |- 5 | npm ci 6 | npm install -g codecov 7 | script: |- 8 | npm run build || travis_terminate 1 9 | ./node_modules/.bin/nyc report --reporter=lcov 10 | codecov 11 | log=$(git log $TRAVIS_COMMIT_RANGE --oneline) 12 | echo $'**Change log:**
'${log//$'\n'/
} 13 | branches: 14 | only: 15 | - master 16 | before_deploy: |- 17 | log=$(git log $TRAVIS_COMMIT_RANGE --oneline) 18 | export TAG=$'v'$TRAVIS_COMMIT_MESSAGE 19 | export BODY=$'**Change log:**
'${log//$'\n'/
} 20 | deploy: 21 | - provider: releases 22 | skip_cleanup: true 23 | api_key: 24 | secure: NyxY7T4REcxEhpAj/VMD4hfv/0Id3yVpo/dTFiVtQhu1JX3ruJ6C6KlGa3k+aHV3Z5Zbwmfm5+azMq+l7Kone/BI7V2m1PbwMRUcEJdBVPfkZKfsIH7rN/7cvJas2pKXCCC/QFooMsFjUebRTZM8KTGxxLwcH3iNwKd2H8fAsCQak0uvjJSbsWmau5L/SLJFoOUpMLDhpH94I/wzZt2vA69A1ziUaMQpYZYV9PM1DwJbnCqtlncwQbmodFsANu7ZlExEEfvPbaLoR5F3zJLd+9aSpRAjfdFNCqwXK3nwyWmMt1ban9EtBjW+OL9ZnBA44Lqm3iZc2b9GCBUX/35XaWBCfhueOA//Eo0QFIV8q3g6iUk/M/PoickBEtax956t/Dkg8vGd914TZ1eYlUa+VNsGIk4iMbtpPXDlFDithplxUgZEY4/m3yMPA1v7KXtjfJ933RqG1Rvym42JsmEDW57i9zfcEhS45ZnxkrJl1hPwT8rEAhFtpkXEBPCQXiHnopXDe2e9WzibkhinIT4wPydFtV62gsAhPJ8YW52v8ZlCCK+XDNi66SYGxXtwm6gpKOsafyTtUOOeDeq1NDRf0RCO3lFYC6z/k/8xrKRVqmYxc/i7efokqBAtMzebGuXEyZL4SMRIXM6ZpDmcGPdxVQKEpoN74oKP39Ji+rhMRbE= 25 | file: README.md 26 | name: Release $TRAVIS_COMMIT_MESSAGE 27 | tag_name: $TAG 28 | body: $BODY 29 | on: 30 | repo: Guseyn/cutie 31 | branch: 32 | - master 33 | condition: $TRAVIS_COMMIT_MESSAGE =~ ^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})$ 34 | - provider: npm 35 | email: guseynism@gmail.com 36 | api_key: 37 | secure: QAIyol5sH60ZcPCWQAF+g6IrCE9xbxnTmZ+3Q+UecFPsZGS3rffL/toMbVptunYGPsavyiIq4mtZ8+8RLWBMdL89EaRiBpKYCK+Tmo7coaGhwh5sSWXFIF63mfrxcnBC7B6+yKawmXAQm99z6Kd4ShdbANSqt4UzQOwFUHHQFWc2QyvGzdY/ZJiRAZeNguAh3J705IA7fohzeGycseBoFf4fB24md4JS/XaqgsZ+Rf0Eio+xV1bMFT4Aibpx+gD0r8wY1X/aLzfsRXAPnokz7RJowzg4ksSDNoIJNrh0RRRdbu2XP9MiocwxdkrmbQPvZI496mJowMTXSbv2L3OPTK7g4VJbfSNfQyujhyVfa2mEgE0vrY4UMavvsBgKM2Y5FpnXzNCG7bjGfXzxgu9gYriiVNJGjFQav1Ia39ndsbWtiLLMTAyyxgL7/A42YmzwQKwqmc19+WJFEg2aD63vRnHIwCGsLbUevq26gR+f6376LxWJthjkaLfiOrl+KBKSh7yRGhc4tmJ0pZ2zLkQPKZvArfJMClIWJMb2L9v3gAX56Lw7LHkPOyrbPBO3kjSFzAStJtisBrZx1RY+HUia9TtNTGc/1pPXk9Z7STCiEiuC/7/ZiiuznrJTvdPBgZBD+lwoKmzFKRG2nYc0j2Ea0DBaZzu2b9pHhJ94rteATe0= 38 | on: 39 | repo: Guseyn/cutie 40 | branch: 41 | - master 42 | condition: $TRAVIS_COMMIT_MESSAGE =~ ^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})$ 43 | -------------------------------------------------------------------------------- /src/AsyncObject.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AsyncTree = require('./AsyncTree') 4 | 5 | /* abstract class */ 6 | 7 | class AsyncObject { 8 | /* 9 | args: any type (including AsyncObject) 10 | */ 11 | constructor (...args) { 12 | this.args = args 13 | this.cache = {} 14 | this.next = undefined 15 | this.asKey = undefined 16 | } 17 | 18 | // TO BE OVERRIDDEN 19 | 20 | asyncCall () { 21 | throw new Error('asyncCall or syncCall must be defined') 22 | } 23 | 24 | syncCall () { 25 | throw new Error('asyncCall or syncCall must be defined') 26 | } 27 | 28 | onError (error) { 29 | throw error 30 | } 31 | 32 | onResult (result) { 33 | return result 34 | } 35 | 36 | /* 37 | Works only if this.continueAfterFail returns true 38 | (in that case this.onError and this.onResult will be ignored), 39 | */ 40 | onErrorAndResult (error, result) { 41 | return error || result 42 | } 43 | 44 | /* 45 | If it returns true, then this.onError and this.onResult will be ignored, 46 | and the represented result of this object 47 | will be returned by this.onErrorAndResult. 48 | */ 49 | continueAfterFail () { 50 | return false 51 | } 52 | 53 | callbackWithError () { 54 | return true 55 | } 56 | 57 | // PUBLIC API 58 | 59 | call () { 60 | this.propagateCache(this) 61 | new AsyncTree(this).create().call() 62 | } 63 | 64 | after (asyncObject) { 65 | this.next = asyncObject 66 | return this 67 | } 68 | 69 | as (key) { 70 | this.asKey = key 71 | return this 72 | } 73 | 74 | // NOT ALLOWED TO BE OVERRIDDEN 75 | 76 | iterateArgs (func) { 77 | this.args.forEach((arg, index) => { 78 | func(arg, index, this.isAsyncObject(arg), this.isEvent(arg)) 79 | }) 80 | } 81 | 82 | hasNoArgs () { 83 | return this.args.length === 0 84 | } 85 | 86 | readyToBeInvoked (readyResultsNum) { 87 | return this.args.length === readyResultsNum 88 | } 89 | 90 | callNextTreeIfExists () { 91 | if (this.next) { 92 | this.propagateCache(this.next) 93 | new AsyncTree(this.next).create().call() 94 | } 95 | } 96 | 97 | propagateCache (arg) { 98 | if (this.isAsyncObject(arg)) { 99 | arg.withCache(this.cache) 100 | arg.iterateArgs(arg => this.propagateCache(arg)) 101 | } 102 | } 103 | 104 | withCache (cache) { 105 | this.cache = cache 106 | return this 107 | } 108 | 109 | saveValueIntoCacheIfNeeded (value) { 110 | if (this.asKey) { 111 | if (this.cache[this.asKey]) { 112 | throw new Error(`There is already value that is cached with key ${this.asKey}`) 113 | } 114 | this.cache[this.asKey] = value 115 | } 116 | return this 117 | } 118 | 119 | isAsyncObject (arg) { 120 | return this.classChain(arg).indexOf('AsyncObject') !== -1 121 | } 122 | 123 | isEvent (arg) { 124 | return this.classChain(arg).indexOf('Event') !== -1 125 | } 126 | 127 | classChain (obj, chain) { 128 | if (!chain) { 129 | chain = [] 130 | } 131 | if (typeof obj === 'function') { 132 | if (!obj.name || obj === Object) { 133 | return chain 134 | } 135 | return this.classChain(Object.getPrototypeOf(obj), chain.concat(obj.name)) 136 | } 137 | if (typeof obj === 'object' && obj !== null) { 138 | return this.classChain(obj.constructor, chain) 139 | } 140 | return chain 141 | } 142 | } 143 | 144 | module.exports = AsyncObject 145 | -------------------------------------------------------------------------------- /src/AsyncTreeNode.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const TreeNode = require('./TreeNode') 4 | 5 | class AsyncTreeNode extends TreeNode { 6 | /* 7 | field: AsyncObject 8 | parent: AsyncTreeNode or NotDefinedAsyncTree 9 | position: int 10 | */ 11 | constructor (field, parent, position) { 12 | super(field, parent, position) 13 | this.argResults = [] 14 | this.readyResultsNum = 0 15 | } 16 | 17 | // PUBLIC 18 | 19 | call () { 20 | let args = this.argResults 21 | try { 22 | let asyncCall = this.field.asyncCall() 23 | if (this.field.callbackWithError()) { 24 | this.invokeAsyncCallWithError(asyncCall, ...args) 25 | } else { 26 | this.invokeAsyncCallWithoutError(asyncCall, ...args) 27 | } 28 | } catch (error) { 29 | if (error.message !== 'asyncCall or syncCall must be defined') { 30 | if (this.field.continueAfterFail()) { 31 | this.field.onErrorAndResult(error) 32 | } else { 33 | this.field.onError(error) 34 | } 35 | } else { 36 | let syncCall = this.field.syncCall() 37 | this.invokeSyncCall(syncCall, ...args) 38 | } 39 | } 40 | } 41 | 42 | isLeaf () { 43 | return this.field.hasNoArgs() 44 | } 45 | 46 | readyToBeInvoked () { 47 | return this.field.readyToBeInvoked(this.readyResultsNum) 48 | } 49 | 50 | hasParent () { 51 | return this.parent instanceof AsyncTreeNode 52 | } 53 | 54 | insertArgumentResult (position, result) { 55 | this.argResults[position] = result 56 | this.readyResultsNum += 1 57 | } 58 | 59 | // PRIVATE 60 | 61 | invokeAsyncCallWithError (asyncCall, ...args) { 62 | asyncCall(...args, (error, ...results) => { 63 | if (!this.processedError(error, ...results)) { 64 | this.processedResult(...results) 65 | } 66 | }) 67 | } 68 | 69 | invokeAsyncCallWithoutError (asyncCall, ...args) { 70 | asyncCall(...args, (...results) => { 71 | this.processedResult(...results) 72 | }) 73 | } 74 | 75 | invokeSyncCall (syncCall, ...args) { 76 | try { 77 | let syncCallResult = syncCall(...args) 78 | this.processedResult(syncCallResult) 79 | } catch (error) { 80 | this.processedError(error) 81 | } 82 | } 83 | 84 | processedError (error, ...results) { 85 | let isProcessed = false 86 | // It's not possible to get rid of null here :( 87 | if (error != null) { 88 | if (this.field.continueAfterFail()) { 89 | let totalResult = this.field.onErrorAndResult(error, ...results) 90 | this.field.saveValueIntoCacheIfNeeded(totalResult) 91 | if (this.hasParent()) { 92 | super.callParent(totalResult) 93 | } else { 94 | this.field.callNextTreeIfExists() 95 | } 96 | } else { 97 | this.field.onError(error) 98 | } 99 | isProcessed = true 100 | } 101 | return isProcessed 102 | } 103 | 104 | processedResult (...results) { 105 | let totalResult 106 | if (this.field.continueAfterFail()) { 107 | totalResult = this.field.onErrorAndResult(null, ...results) 108 | } else { 109 | totalResult = this.field.onResult(...results) 110 | } 111 | this.field.saveValueIntoCacheIfNeeded(totalResult) 112 | if (this.hasParent()) { 113 | super.callParent(totalResult) 114 | } else { 115 | this.field.callNextTreeIfExists() 116 | } 117 | return true 118 | } 119 | } 120 | 121 | module.exports = AsyncTreeNode 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/@cuties/cutie.svg)](https://npmjs.org/package/@cuties/cutie) 4 | [![Build Status](https://travis-ci.org/Guseyn/cutie.svg?branch=master)](https://travis-ci.org/Guseyn/cutie) 5 | [![codecov](https://codecov.io/gh/Guseyn/cutie/branch/master/graph/badge.svg)](https://codecov.io/gh/Guseyn/cutie) 6 | 7 | **Cutie** is a lightweight library that implements [Async Tree Pattern](https://guseyn.com/pdf/Async_Tree_Pattern.pdf). 8 | 9 | # Motivation 10 | Let's say we want to read content from a file and write it to another one. And all these operations are asynchronous, of course. So, instead of writing something like this: 11 | ```js 12 | fs.readFile('./../file1.txt', 'utf8', (err, result) => { 13 | if (err != null) { 14 | throw err 15 | } 16 | 17 | fs.writeFile('./../file2.txt', result, (err) => { 18 | if (err != null) { 19 | throw err 20 | } 21 | }) 22 | }) 23 | ``` 24 | we can design our code in the following style: 25 | ```js 26 | new WrittenFile( 27 | './../file2.txt', 28 | new ReadDataByPath('./../file1.txt', 'utf8') 29 | ).call() 30 | ``` 31 | # How to use 32 | You can use Cutie as a dependency via npm: 33 | `npm install @cuties/cutie` 34 | ```js 35 | const AsyncObject = require('@cuties/cutie').AsyncObject 36 | const fs = require('fs') 37 | 38 | // Represents file as path 39 | class WrittenFile extends AsyncObject { 40 | constructor (path, content) { 41 | super(path, content) 42 | } 43 | 44 | asyncCall () { 45 | return (path, content, callback) => { 46 | this.path = path 47 | fs.writeFile(path, content, callback) 48 | } 49 | } 50 | 51 | onResult() { 52 | return this.path 53 | } 54 | } 55 | ``` 56 | ```js 57 | const AsyncObject = require('@cuties/cutie').AsyncObject 58 | const fs = require('fs') 59 | 60 | // Represents buffer or string 61 | class ReadDataByPath extends AsyncObject { 62 | constructor (path, encoding) { 63 | super(path, encoding); 64 | } 65 | 66 | asyncCall () { 67 | return fs.readFile 68 | } 69 | } 70 | ``` 71 | AsyncObject also provides methods `OnResult` and `OnError`, so that you can process the result (it's provided by callback by default) from async call and handle an error in the specific way (error is being thrown by default). 72 | 73 | Let's say we want to read a json file and parse all information from there. Cutie provides two ways. First of them is just to create `ParsedJSON` async object like this: 74 | ```js 75 | const AsyncObject = require('@cuties/cutie').AsyncObject; 76 | const fs = require('fs'); 77 | 78 | class ParsedJSON extends AsyncObject { 79 | constructor (path, encoding) { 80 | super(path, encoding) 81 | } 82 | 83 | asyncCall () { 84 | return fs.readFile 85 | } 86 | 87 | onResult (result) { 88 | return JSON.parse(result) 89 | } 90 | } 91 | 92 | // usage 93 | new ParsedJSON('./../file.txt', 'utf8').call() 94 | ``` 95 | `ParsedJSON` also could be designed like this: 96 | ```js 97 | const fs = require('fs') 98 | const ReadDataByPath = require('./ReadDataByPath') 99 | 100 | class ParsedJSON extends ReadDataByPath { 101 | constructor (path, encoding) { 102 | super(path, encoding) 103 | } 104 | 105 | onResult (result) { 106 | return JSON.parse(result) 107 | } 108 | } 109 | 110 | // usage 111 | new ParsedJSON('./../file.txt', 'utf8').call(); 112 | ``` 113 | Or you can use `ReadDataByPath` with `ParsedJSON` that looks like this: 114 | ```js 115 | const AsyncObject = require('@cuties/cutie').AsyncObject 116 | const fs = require('fs') 117 | const ReadDataByPath = require('./ReadDataByPath') 118 | 119 | class ParsedJSON extends AsyncObject { 120 | constructor (text) { 121 | super(text) 122 | } 123 | 124 | /* 125 | you can't call here async operations with I/O 126 | */ 127 | syncCall () { 128 | return JSON.parse 129 | } 130 | } 131 | 132 | // usage 133 | new ParsedJSON( 134 | new ReadDataByPath('./../file.txt', 'utf8') 135 | ).call() 136 | ``` 137 | ## Read more 138 | 139 | 1. [Async Tree Pattern](https://guseyn.com/pdf/Async_Tree_Pattern.pdf) 140 | 2. [Async Objects instead of Async Calls](https://guseyn.com/posts/async-objects-instead-of-async-calls) 141 | 142 | ## Run test 143 | 144 | `npm test` 145 | 146 | ## Run build 147 | 148 | `npm run build` 149 | 150 | ## Libraries that use cutie 151 | 152 | [node-test-executor](https://github.com/Guseyn/node-test-executor), [cutie-is](https://github.com/Guseyn/cutie-is), [cutie-assert](https://github.com/Guseyn/cutie-assert), [cutie-fs](https://github.com/Guseyn/cutie-fs), [cutie-http](https://github.com/Guseyn/cutie-http), [cutie-https](https://github.com/Guseyn/cutie-http), [cutie-rest](https://github.com/Guseyn/cutie-rest), [cutie-buffer](https://github.com/Guseyn/cutie-buffer), [cutie-error](https://github.com/Guseyn/cutie-error), [cutie-date](https://github.com/Guseyn/cutie-date), [cutie-json](https://github.com/Guseyn/cutie-json), [cutie-event](https://github.com/Guseyn/cutie-event), [cutie-stream](https://github.com/Guseyn/cutie-stream), [cutie-object](https://github.com/Guseyn/cutie-object), [cutie-process](https://github.com/Guseyn/cutie-process), [cutie-iterator](https://github.com/Guseyn/cutie-iterator), [cutie-path](https://github.com/Guseyn/cutie-path), [cutie-if-else](https://github.com/Guseyn/cutie-if-else), [cutie-cluster](https://github.com/Guseyn/cutie-cluster), [page-static-generator](https://github.com/Guseyn/page-static-generator) and many others. 153 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const AsyncObject = require('./../src/AsyncObject') 5 | const Event = require('./../src/Event') 6 | const as = require('./../src/As') 7 | const StrictEqualAssertion = require('./StrictEqualAssertion') 8 | const DeepStrictEqualAssertion = require('./DeepStrictEqualAssertion') 9 | const AsyncMaxNum = require('./AsyncMaxNum') 10 | const SyncMaxNum = require('./AsyncMaxNum') 11 | const BrokenAsyncObject = require('./BrokenAsyncObject') 12 | const SafeAsyncObject = require('./SafeAsyncObject') 13 | const AsyncObjectWithoutError = require('./AsyncObjectWithoutError') 14 | const EventOfAsyncObjectWithEvent = require('./EventOfAsyncObjectWithEvent') 15 | const BrokenEvent = require('./BrokenEvent') 16 | const InvokedEvent = require('./InvokedEvent') 17 | const BrokenTreeNode = require('./BrokenTreeNode') 18 | const AsyncObjectWithArgs = require('./AsyncObjectWithArgs') 19 | const AsyncObjectWithAssertedErrorInCallback = require('./AsyncObjectWithAssertedErrorInCallback') 20 | const AsyncObjectWithAssertedSafeErrorInCallback = require('./AsyncObjectWithAssertedSafeErrorInCallback') 21 | const AsyncObjectWithAssertedErrorInDefinedCall = require('./AsyncObjectWithAssertedErrorInDefinedCall') 22 | const AsyncObjectWithAssertedSafeErrorInDefinedCall = require('./AsyncObjectWithAssertedSafeErrorInDefinedCall') 23 | const AsyncObjectWithAssertedResultInCallback = require('./AsyncObjectWithAssertedResultInCallback') 24 | const AsyncObjectWithAssertedSafeResultInCallback = require('./AsyncObjectWithAssertedSafeResultInCallback') 25 | const AsyncObjectWithAssertedSafeErrorAndResultInCallback = require('./AsyncObjectWithAssertedSafeErrorAndResultInCallback') 26 | 27 | let testAsyncObject = 28 | new AsyncMaxNum( 29 | new AsyncMaxNum(1, 3, 30 | new AsyncMaxNum(1, 2, 4).as('max1') 31 | ).as('max2'), 32 | new AsyncMaxNum(2, 33 | new SyncMaxNum(3, 4, 34 | new SyncMaxNum(5, 4, 6).as('max3') 35 | ).as('max4'), 1 36 | ).as('max5'), 37 | new SyncMaxNum(1, 2, 8).as('max6') 38 | ).as('max7') 39 | .after( 40 | new StrictEqualAssertion( 41 | new StrictEqualAssertion( 42 | new AsyncMaxNum( 43 | as('max1'), 44 | new AsyncMaxNum( 45 | new SyncMaxNum( 46 | 3, as('max2'), as('max3') 47 | ), 4, as('max4') 48 | ), 49 | new AsyncMaxNum(4, 50 | new SyncMaxNum(as('max5'), 4, 51 | new AsyncMaxNum(4, 6, 2) 52 | ), as('max6') 53 | ) 54 | ), 55 | new AsyncMaxNum(3, 4, 8) 56 | ), 57 | new AsyncMaxNum(1, 4, 8).as('max8') 58 | ).after( 59 | new StrictEqualAssertion(8, as('max8')) 60 | ) 61 | ) 62 | 63 | testAsyncObject.call() 64 | 65 | // can't save value into cache with the same key 66 | try { 67 | new SyncMaxNum(1, 3, 68 | new SyncMaxNum(1, 2, 4).as('max1') 69 | ).after( 70 | new SyncMaxNum(1, 2, 4).as('max1') 71 | ).call() 72 | } catch (err) { 73 | assert.deepStrictEqual(err, new Error('There is already value that is cached with key max1')) 74 | } 75 | 76 | let testAsyncObjectWithFailedAs = 77 | new StrictEqualAssertion( 78 | new AsyncMaxNum(1, 2, as('max')) 79 | ) 80 | 81 | // we can do it, because here AsyncMaxNum does not work with I/O asynchronously 82 | try { 83 | testAsyncObjectWithFailedAs.call() 84 | } catch (err) { 85 | assert.deepStrictEqual(err, new Error('There is no value that is cached with key max')) 86 | } 87 | 88 | let testAsyncObjectWithoutDefinedCall = 89 | new BrokenAsyncObject() 90 | 91 | try { 92 | testAsyncObjectWithoutDefinedCall.call() 93 | } catch (err) { 94 | assert.deepStrictEqual(err, new Error('asyncCall or syncCall must be defined')) 95 | } 96 | 97 | let testAsyncObjectWithSafeError = 98 | new DeepStrictEqualAssertion( 99 | new SafeAsyncObject(), 100 | new Error('safe error') 101 | ) 102 | 103 | testAsyncObjectWithSafeError.call() 104 | 105 | let testAsyncObjectWithoutError = 106 | new StrictEqualAssertion( 107 | new AsyncObjectWithoutError(), 'value' 108 | ) 109 | 110 | testAsyncObjectWithoutError.call() 111 | 112 | let testAsyncObjectWithBrokenEvent = 113 | new InvokedEvent( 114 | new EventOfAsyncObjectWithEvent( 115 | new BrokenEvent() 116 | ) 117 | ) 118 | 119 | try { 120 | testAsyncObjectWithBrokenEvent.call() 121 | } catch (err) { 122 | assert.deepStrictEqual(err, new Error('Method body must be overriden with arguments of the event/eventListener you call')) 123 | } 124 | 125 | let brokenTreeNode = new BrokenTreeNode() 126 | 127 | try { 128 | brokenTreeNode.isLeaf() 129 | } catch (err) { 130 | assert.deepStrictEqual(err, new Error('isLeaf must be overridden')) 131 | } 132 | 133 | try { 134 | brokenTreeNode.call() 135 | } catch (err) { 136 | assert.deepStrictEqual(err, new Error('call must be overridden and insert result into parent node')) 137 | } 138 | 139 | // just test safe error in callback 140 | new AsyncObjectWithAssertedSafeErrorInCallback().call() 141 | 142 | // test safe error in callback (with parent async tree node) 143 | new AsyncObjectWithArgs( 144 | new AsyncObjectWithAssertedSafeErrorInCallback() 145 | ).call() 146 | 147 | // just test error in callback 148 | new AsyncObjectWithAssertedErrorInCallback().call() 149 | 150 | // test error in callback (with parent async tree node) 151 | new AsyncObjectWithArgs( 152 | new AsyncObjectWithAssertedErrorInCallback() 153 | ).call() 154 | 155 | // just test error in definedCall 156 | new AsyncObjectWithAssertedErrorInDefinedCall().call() 157 | 158 | // test error in definedCall (with parent async tree node) 159 | new AsyncObjectWithArgs( 160 | new AsyncObjectWithAssertedErrorInDefinedCall() 161 | ).call() 162 | 163 | // just test safe error in definedCall 164 | new AsyncObjectWithAssertedSafeErrorInDefinedCall().call() 165 | 166 | // test safe error in definedCall (with parent async tree node) 167 | new AsyncObjectWithArgs( 168 | new AsyncObjectWithAssertedSafeErrorInDefinedCall() 169 | ).call() 170 | 171 | // just test result in callback 172 | new AsyncObjectWithAssertedResultInCallback().call() 173 | 174 | // test result in callback (with parent async tree node) 175 | new AsyncObjectWithArgs( 176 | new AsyncObjectWithAssertedResultInCallback() 177 | ).call() 178 | 179 | // just test safe result in callback 180 | new AsyncObjectWithAssertedSafeResultInCallback().call() 181 | 182 | // test safe result in callback (with parent async tree node) 183 | new AsyncObjectWithArgs( 184 | new AsyncObjectWithAssertedSafeResultInCallback() 185 | ).call() 186 | 187 | // just test safe error and safe result in callback 188 | new AsyncObjectWithAssertedSafeErrorAndResultInCallback().call() 189 | 190 | // test safe error and safe result in callback (with parent async tree node) 191 | new AsyncObjectWithArgs( 192 | new AsyncObjectWithAssertedSafeErrorAndResultInCallback() 193 | ).call() 194 | 195 | class A extends AsyncObject {} 196 | class B extends A {} 197 | class C extends B {} 198 | class E extends Event {} 199 | class D extends E {} 200 | class F extends D {} 201 | class N {} 202 | 203 | assert(new A().isAsyncObject(new A())) 204 | assert(new A().isAsyncObject(new B())) 205 | assert(new A().isAsyncObject(new C())) 206 | assert(!new A().isAsyncObject(new N())) 207 | assert(new A().isEvent(new E())) 208 | assert(new A().isEvent(new D())) 209 | assert(new A().isEvent(new F())) 210 | assert(!new A().isEvent(new N())) 211 | --------------------------------------------------------------------------------