From 53ac8ab08cd61211a31c4e7bd5ef13e507258256 Mon Sep 17 00:00:00 2001 From: Kenneth Brubaker Date: Sat, 14 Jan 2017 21:25:24 -0600 Subject: [PATCH] =?UTF-8?q?feat(devenv):=20Build=20single=20NPM=20package?= =?UTF-8?q?=20=F0=9F=94=83=20=E2=9C=A8=20=E2=9A=96=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding message source... * Conformed package modules to typescript idiom. Adding util module. * Working on core.StructuredError etc. * completed naming and location of ts files. * Applied ts 2.0 features and settings * Conformed @jali/notes to new tsconfig style. * Can now run tsc from modules folder. * Added npm build script * One working test! Conformed to tslint. Added babel transforms. * Added argument validators. * reformated to 2 space indents. completed validators. * Renamed modules to packages * Making argument error test helper. * Added Error tests Added Argument Error tests and started arg verification function tests. Also changed all double quotes to single quotes * Adding argument validator tests. * Progressing on argument validator unit tests * Added nyc code coverage to project * Updated enviornment latest version of vscode. * Updated vscode settings for unix line endings. * Added gitattibutes to enforce unix line endings * Unit tests progressed through verifyNonEmpty included build in test step Added babel, ava, and nyc settings to package.json Ensured inline sourcemaps for test but not for production. Some minor error message changes. Changed name of validators to verifiers * continuing to unit test validators * writing unit tests Completed verifiers Started iterables * Reached 100 percent code coverage of utils fixed nyc exclude added namespaces to package modules. * Tried to add TypeDoc. Failed Minor bug fix Trying out OpenHub badge. * Finalized badges in README * Administrivia fiddled with the package.json scripts Added a readme badge * Tried to use dgeni * Got esdoc integrated * Adding esdoc manual pages. * Working on documentation Added example code. Added option function message to argument verifiers. * Docs and configuration Added code documentation Added clean scripts to package.json * Adding example source * Added cla and google analytics to readem * Example runner with decorators and markers * Initial compile-fix of examples. * integrated generated examples into build process * Added tests. * lintfixed. * Added lint to dev build script * Adding documentation. * Missed change. * Fixed document links * Adding api documentation * Adding API docs * Added Iterables functions matching Array methods * Adding Iterables examples * modified find to subsume first/firstOrDefault. * Complete iterable examples * Adding iterables unit tests * Complete unit tests for iterables. * Address bithound dependency issues * Adjusted bithound reporting * Testing bithound lint * bithound lint fix * Incorporated tslint settings from ng-cli beta 11 * Turned bithound linting off temporarily. * Made minor change to test versioneye whitelist. * Added spare line to test version eye whitelist. * Small change to test versioneye whitelist * Changed file to test semaphoreci integration * Removed typings from gitignore * Started build:prod * Completed basic webpack functionality. * Improved package build * Fixed Node 6 LTE compile error Also updated to latest dependencies. * Added linting, examples, docs to npm test * Configuration and Documentation Configuration: Updated to Ubuntu 16.04 Updated chef berksfiles to latest Updated vagrant plugins to latest Updated npm packages to latest Documentation Added DESIGN.md Formatting and minor changes to CONTRIBUTING.md * markdown changes Formatting Updated ecmascript proposals. * Documentation and Style changes Updated ecmaspec changes Added markdown linter Fixed rb linting errors updated typescript and webpack versions * Updated packages to latest. * Formatting and build changes. - Had to revert esdoc due to visual bug - Added more topics to complete in design document * Fixed webpackfile.js + documentation * Fixed links in md. * Fixed md links * Muted bithound failure Muted esdoc because there is a bug in 0.5.x as noted in Bug #80 --- .bithoundrc | 28 + .clang-format | 3 - .eslintignore | 10 + .eslintrc.json | 72 + .gitattributes | 2 + .github/ISSUE_TEMPLATE.md | 193 +- .gitignore | 7 +- .markdownlint.json | 19 + .vscode/cSpell.json | 76 + .vscode/launch.json | 110 + .vscode/settings.json | 16 + .vscode/tasks.json | 49 + Berksfile | 2 +- CONTRIBUTING.md | 232 +- CREDITS.md | 71 +- DESIGN.md | 199 + ISSUE-TEMPLATE-INSTRUCTIONS.md | 303 ++ README.md | 190 +- Vagrantfile | 12 +- config/dgeni/index.js | 96 + config/dgeni/templates/class.template.html | 36 + docs/esdoc/overview.md | 154 + ecmascript-proposals.md | 144 + esdoc.json | 14 + examples/example-context.ts | 189 + examples/example-metadata.ts | 8 + examples/example-runner-options.ts | 11 + examples/example-runner.ts | 320 ++ examples/example.ts | 29 + examples/file-path.ts | 7 + examples/helpers.ts | 0 examples/index.ts | 15 + examples/packages/util.example.ts | 432 ++ examples/tsconfig.json | 40 + package.json | 193 +- packages/@jali/core/index.ts | 9 + packages/@jali/core/iterables/index.ts | 1 + packages/@jali/core/package.json | 53 + packages/@jali/core/src/message-priority.ts | 24 + packages/@jali/core/src/message-severity.ts | 45 + .../src/notification-message-iterables.ts | 18 + .../@jali/core/src/notification-message.ts | 14 + packages/@jali/core/src/structured-error.ts | 57 + packages/@jali/core/src/type-guards.ts | 10 + packages/@jali/core/tsconfig-build.json | 35 + packages/@jali/core/type-guards/index.ts | 1 + packages/@jali/note/es2015.tsconfig.json | 26 + packages/@jali/note/es5.tsconfig.json | 26 + packages/@jali/note/index.ts | 8 + packages/@jali/note/package.json | 53 + packages/@jali/note/src/message-code.md | 51 + packages/@jali/note/src/message-code.ts | 25 + .../@jali/note/src/message-encoding-data.ts | 14 + .../note/src/message-encoding-segment-data.ts | 6 + .../note/src/message-encoding-version.ts | 13 + packages/@jali/note/src/message-encoding.ts | 15 + packages/@jali/note/src/standard-encodings.ts | 6 + .../src/standard-message-encoding-version.ts | 55 + .../note/src/standard-message-encoding.ts | 87 + packages/@jali/note/src/typed-message.ts | 24 + packages/@jali/note/tsconfig-build.json | 33 + packages/@jali/package.json | 42 + packages/@jali/util/.npmignore | 2 + packages/@jali/util/README.md | 99 + packages/@jali/util/errors/index.ts | 12 + packages/@jali/util/index.ts | 5 + packages/@jali/util/iterables/index.ts | 1 + packages/@jali/util/package.json | 63 + .../util/src/argument-empty-string-error.ts | 37 + packages/@jali/util/src/argument-error.ts | 49 + .../@jali/util/src/argument-false-error.ts | 35 + .../@jali/util/src/argument-falsy-error.ts | 54 + packages/@jali/util/src/argument-nan-error.ts | 35 + .../@jali/util/src/argument-null-error.ts | 36 + .../@jali/util/src/argument-type-error.ts | 54 + .../util/src/argument-undefined-error.ts | 35 + packages/@jali/util/src/argument-verifiers.ts | 719 +++ .../src/argument-whitespace-string-error.ts | 39 + .../@jali/util/src/argument-zero-error.ts | 35 + .../@jali/util/src/invalid-state-error.ts | 10 + packages/@jali/util/src/iterables.ts | 682 +++ packages/@jali/util/src/type-guards.ts | 52 + .../argument-empty-string-error.unit.test.ts | 95 + .../util/test/argument-error.unit.test.ts | 96 + .../test/argument-false-error.unit.test.ts | 95 + .../test/argument-falsy-error.unit.test.ts | 95 + .../util/test/argument-nan-error.unit.test.ts | 95 + .../test/argument-null-error.unit.test.ts | 97 + .../test/argument-type-error.unit.test.ts | 94 + .../argument-undefined-error.unit.test.ts | 97 + .../util/test/argument-verifiers.unit.test.ts | 4083 +++++++++++++++++ .../argument-whitespace-error.unit.test.ts | 95 + .../test/argument-zero-error.unit.test.ts | 95 + .../util/test/iterables-as-array.unit.test.ts | 355 ++ .../test/iterables-as-iterable.unit.test.ts | 354 ++ .../util/test/iterables-concat.unit.test.ts | 97 + .../util/test/iterables-every.unit.test.ts | 168 + .../util/test/iterables-filter.unit.test.ts | 44 + .../util/test/iterables-find.unit.test.ts | 163 + .../util/test/iterables-includes.unit.test.ts | 300 ++ .../util/test/iterables-map.unit.test.ts | 92 + .../util/test/iterables-reduce.unit.test.ts | 187 + .../util/test/iterables-slice.unit.test.ts | 225 + .../util/test/iterables-some.unit.test.ts | 109 + .../util/test/iterables-to-map.unit.test.ts | 45 + .../@jali/util/test/type-guards.unit.test.ts | 358 ++ .../util/testing/argument-error-helpers.ts | 70 + packages/@jali/util/testing/helpers.ts | 39 + packages/@jali/util/testing/index.ts | 7 + .../@jali/util/testing/iterables-helpers.ts | 67 + packages/@jali/util/testing/product-epic.ts | 8 + packages/@jali/util/testing/repo-package.ts | 8 + .../@jali/util/testing/test-description.ts | 15 + .../@jali/util/testing/test-disposition.ts | 6 + packages/@jali/util/testing/test-type.ts | 7 + packages/@jali/util/tsconfig-build.json | 40 + packages/@jali/util/type-guards/index.ts | 1 + packages/@jali/util/webpackfile.js | 107 + packages/tsconfig.json | 33 + site-cookbooks/main/Berksfile | 2 +- site-cookbooks/main/CHANGELOG.md | 4 + site-cookbooks/main/metadata.rb | 6 +- site-cookbooks/main/recipes/default.rb | 22 +- tsconfig.json | 35 + tslint.json | 90 +- typedoc.json | 21 + typings/declarations/declarations.d.ts | 10 + typings/index.d.ts | 1 + webpackfile.js | 22 +- 129 files changed, 13922 insertions(+), 320 deletions(-) create mode 100644 .bithoundrc delete mode 100644 .clang-format create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .gitattributes create mode 100644 .markdownlint.json create mode 100644 .vscode/cSpell.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 DESIGN.md create mode 100644 ISSUE-TEMPLATE-INSTRUCTIONS.md create mode 100644 config/dgeni/index.js create mode 100644 config/dgeni/templates/class.template.html create mode 100644 docs/esdoc/overview.md create mode 100644 ecmascript-proposals.md create mode 100644 esdoc.json create mode 100644 examples/example-context.ts create mode 100644 examples/example-metadata.ts create mode 100644 examples/example-runner-options.ts create mode 100644 examples/example-runner.ts create mode 100644 examples/example.ts create mode 100644 examples/file-path.ts create mode 100644 examples/helpers.ts create mode 100644 examples/index.ts create mode 100644 examples/packages/util.example.ts create mode 100644 examples/tsconfig.json create mode 100644 packages/@jali/core/index.ts create mode 100644 packages/@jali/core/iterables/index.ts create mode 100644 packages/@jali/core/package.json create mode 100644 packages/@jali/core/src/message-priority.ts create mode 100644 packages/@jali/core/src/message-severity.ts create mode 100644 packages/@jali/core/src/notification-message-iterables.ts create mode 100644 packages/@jali/core/src/notification-message.ts create mode 100644 packages/@jali/core/src/structured-error.ts create mode 100644 packages/@jali/core/src/type-guards.ts create mode 100644 packages/@jali/core/tsconfig-build.json create mode 100644 packages/@jali/core/type-guards/index.ts create mode 100644 packages/@jali/note/es2015.tsconfig.json create mode 100644 packages/@jali/note/es5.tsconfig.json create mode 100644 packages/@jali/note/index.ts create mode 100644 packages/@jali/note/package.json create mode 100644 packages/@jali/note/src/message-code.md create mode 100644 packages/@jali/note/src/message-code.ts create mode 100644 packages/@jali/note/src/message-encoding-data.ts create mode 100644 packages/@jali/note/src/message-encoding-segment-data.ts create mode 100644 packages/@jali/note/src/message-encoding-version.ts create mode 100644 packages/@jali/note/src/message-encoding.ts create mode 100644 packages/@jali/note/src/standard-encodings.ts create mode 100644 packages/@jali/note/src/standard-message-encoding-version.ts create mode 100644 packages/@jali/note/src/standard-message-encoding.ts create mode 100644 packages/@jali/note/src/typed-message.ts create mode 100644 packages/@jali/note/tsconfig-build.json create mode 100644 packages/@jali/package.json create mode 100644 packages/@jali/util/.npmignore create mode 100644 packages/@jali/util/README.md create mode 100644 packages/@jali/util/errors/index.ts create mode 100644 packages/@jali/util/index.ts create mode 100644 packages/@jali/util/iterables/index.ts create mode 100644 packages/@jali/util/package.json create mode 100644 packages/@jali/util/src/argument-empty-string-error.ts create mode 100644 packages/@jali/util/src/argument-error.ts create mode 100644 packages/@jali/util/src/argument-false-error.ts create mode 100644 packages/@jali/util/src/argument-falsy-error.ts create mode 100644 packages/@jali/util/src/argument-nan-error.ts create mode 100644 packages/@jali/util/src/argument-null-error.ts create mode 100644 packages/@jali/util/src/argument-type-error.ts create mode 100644 packages/@jali/util/src/argument-undefined-error.ts create mode 100644 packages/@jali/util/src/argument-verifiers.ts create mode 100644 packages/@jali/util/src/argument-whitespace-string-error.ts create mode 100644 packages/@jali/util/src/argument-zero-error.ts create mode 100644 packages/@jali/util/src/invalid-state-error.ts create mode 100644 packages/@jali/util/src/iterables.ts create mode 100644 packages/@jali/util/src/type-guards.ts create mode 100644 packages/@jali/util/test/argument-empty-string-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-false-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-falsy-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-nan-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-null-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-type-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-undefined-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-verifiers.unit.test.ts create mode 100644 packages/@jali/util/test/argument-whitespace-error.unit.test.ts create mode 100644 packages/@jali/util/test/argument-zero-error.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-as-array.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-as-iterable.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-concat.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-every.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-filter.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-find.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-includes.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-map.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-reduce.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-slice.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-some.unit.test.ts create mode 100644 packages/@jali/util/test/iterables-to-map.unit.test.ts create mode 100644 packages/@jali/util/test/type-guards.unit.test.ts create mode 100644 packages/@jali/util/testing/argument-error-helpers.ts create mode 100644 packages/@jali/util/testing/helpers.ts create mode 100644 packages/@jali/util/testing/index.ts create mode 100644 packages/@jali/util/testing/iterables-helpers.ts create mode 100644 packages/@jali/util/testing/product-epic.ts create mode 100644 packages/@jali/util/testing/repo-package.ts create mode 100644 packages/@jali/util/testing/test-description.ts create mode 100644 packages/@jali/util/testing/test-disposition.ts create mode 100644 packages/@jali/util/testing/test-type.ts create mode 100644 packages/@jali/util/tsconfig-build.json create mode 100644 packages/@jali/util/type-guards/index.ts create mode 100644 packages/@jali/util/webpackfile.js create mode 100644 packages/tsconfig.json create mode 100644 tsconfig.json create mode 100644 typedoc.json create mode 100644 typings/declarations/declarations.d.ts create mode 100644 typings/index.d.ts diff --git a/.bithoundrc b/.bithoundrc new file mode 100644 index 0000000..1a3e559 --- /dev/null +++ b/.bithoundrc @@ -0,0 +1,28 @@ +{ + "critics": { + "lint": {"engine": "none"}, + "wc": { "limit": 5000 } + }, + "ignore": [ + "**/node_modules/**", + "build/**", + "config/**", + "coverage/**", + "dist/**", + "examples/**", + "packages/@jali/core/**", + "packages/@jali/note/**", + "webpackfile.js" + ], + "test": [ + "**/test/**", + "**/tests/**", + "**/spec/**", + "**/specs/**" + ], + "dependencies": { + "mute": [ + "esdoc" + ] + } +} diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 8d1c3c3..0000000 --- a/.clang-format +++ /dev/null @@ -1,3 +0,0 @@ -Language: JavaScript -BasedOnStyle: Google -ColumnLimit: 100 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..ae139a5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +######################################### +# Angular CLI +----------------------------------------- +# compiled output +/dist +/tmp + +# e2e +/e2e/*.js +/e2e/*.map diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..ad06e2f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,72 @@ +{ + "env": { + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 7, + "sourceType": "module" + }, + "root": true, + "rules": { + "spaced-comment": "error", + "curly": "error", + "eol-last": "error", + "guard-for-in": "error", + "indent": [ + "error", + 2 + ], + "no-duplicate-case": "error", + "no-extra-label": "error", + "no-unused-labels": "error", + "no-label-var": "error", + "max-len": [ + "error", + 100 + ], + "no-caller": "error", + "prefer-rest-params": "error", + "no-bitwise": "error", + + "no-console": [ + "error", + { + "allow": ["assert"] + } + ], + "no-new-wrappers": "error", + "no-debugger": "error", + "no-dupe-keys": "error", + "no-redeclare": "error", + "no-empty": "off", + "no-eval": "error", + "no-shadow": "error", + //"no-string-literal": true, + "no-fallthrough": "error", + "no-trailing-spaces": "error", + "no-unused-expressions": "error", + "no-unused-vars": "error", + "no-unreachable": "error", + "no-use-before-define": "error", + "no-var": "error", + "sort-keys": "error", + "quotes": [ + "error", + "single", + { + + "avoidEscape": true, + "allowTemplateLiterals": true + } + ], + "radix": "error", + "semi": [ + "error", + "always" + ], + "space-before-blocks": "error", + "space-infix-ops": "error", + "space-unary-ops": "error" + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ea16f2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Unix (lf--\n) line endings +* text eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9e4687c..9a47328 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,174 +1,56 @@ - -[//]: # ( Note: Comment format explained by: http://stackoverflow.com/a/32190021) -[//]: # ( ) -[//]: # ( # Jali https://github.com/latticework/jali GitHub Issue ) -[//]: # ( ) -[//]: # ( This template supports four types of issues: Question, Idea, Bugs, ) -[//]: # ( and Enhancements. Please fill out the appropriate template and remove ) -[//]: # ( others. ) -[//]: # ( ) -[//]: # ( ## General Instructions: ) -[//]: # ( ) -[//]: # ( ### Workflow ) -[//]: # ( The Jali repository uses ZenHub, https://www.zenhub.com/, for ) -[//]: # ( project management. See CONTRIBUTING.md for the proper workflow for ) -[//]: # ( Jali GitHub issues. A new issue submitted by a non-contributor or ) -[//]: # ( non-core contributor should be entered using the Question or Idea ) -[//]: # ( form. ) -[//]: # ( ) -[//]: # ( ## Commit types ) -[//]: # ( The issue can be focused by specifying what kind of commit is ) -[//]: # ( envisioned by a change. If more than one type of commit is associated ) -[//]: # ( with the issue, consider how you can break it up into multiple ) -[//]: # ( issues. ) -[//]: # ( ) -[//]: # ( - commit-type-name commit-type-emoji ) -[//]: # ( - ---------------- ----------------- ) -[//]: # ( - feat ✨ ) -[//]: # ( - fix πŸ”§ ) -[//]: # ( - docs πŸ“„ ) -[//]: # ( - style πŸ’„ ) -[//]: # ( - refactor πŸ“ ) -[//]: # ( - perf πŸƒ ) -[//]: # ( - test πŸ”¬ ) -[//]: # ( - chore πŸ”¨ ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ## Template forms ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ### QUESTION ❓ :question: ) -[//]: # ( ) -[//]: # ( Usage questions should be asked at http://stackoverflow.com/. ) -[//]: # ( However, not all questions are appropriate for StackOverflow. ) -[//]: # ( See http://stackoverflow.com/tour for what kinds of questions ) -[//]: # ( are appropriate for StackOverflow. Use the `jali` tag for questions ) -[//]: # ( about Jali. Other questions should be asked in this repo by creating ) -[//]: # ( an issue using the Question form. ) -[//]: # ( ) -[//]: # ( Instructions: ) -[//]: # ( * Title ) -[//]: # ( - Use a short interrogative sentance (i.e. a question) that ) -[//]: # ( should be under 120 characters in length. ) -[//]: # ( - End with the emoji sequence: `❓` `` `🎁` ) -[//]: # ( * Type ) -[//]: # ( - Use the commit types most closely related to your question. ) -[//]: # ( * Question details ) -[//]: # ( - Feel free to add formatting. For log dumps and other large ) -[//]: # ( data, attach documents. ) +[//]: # ( ) +[//]: # ( ) +[//]: # ( ) +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) +[//]: # (Note: Comment format explained by: ) +[//]: # (http://stackoverflow.com/a/32190021 ) + +> Important: Following the [ISSUE-TEMPLATE-INSTRUCTIONS.md](https://github.com/latticework/jali/blob/master/ISSUE-TEMPLATE-INSTRUCTIONS.md), +> choose the appropriate template form, `Question`, `Idea`, `Bug`, +> `Enhancement`, or `Epic`. Delete the other forms, and fill out the +> remaining form according to the instructions. + +[//]: # ( ############################################################ ) +[//]: # ( # FORM: Question ) # Question: `` ## Details -[//]: # ( ) -[//]: # ( ) -[//]: # ( ### IDEA πŸ’‘ :bulb: ) -[//]: # ( ) -[//]: # ( An Idea is a suggested change to the system. If you intend to submit ) -[//]: # ( a GitHub pull request, you should submit an idea first, then ) -[//]: # ( reference the Idea from the PR. ) -[//]: # ( ) -[//]: # ( Instructions: ) -[//]: # ( * Title ) -[//]: # ( - Use a short imperative verb phrase. It should be under 120 ) -[//]: # ( characters in length. ) -[//]: # ( - End with the emoji sequence: `πŸ’‘` `` `🎁` ) -[//]: # ( * Type ) -[//]: # ( - Use the commit types most closely related to your question. ) -[//]: # ( * Idea details ) -[//]: # ( - Feel free to add formatting. For log dumps and other large ) -[//]: # ( data, attach documents. If you have created a PR or are a ) -[//]: # ( non-core contributor, keep the **Idea** header but include the ) -[//]: # ( the enhancement form body. ) + +[//]: # ( ############################################################ ) +[//]: # ( # FORM: Idea ) # Idea: `` ## Details -[//]: # ( ) -[//]: # ( ) -[//]: # ( ### BUG 🐞 :beetle: ) -[//]: # ( ) -[//]: # ( The Bug form should only be used by core members; others should use ) -[//]: # ( the Idea form. A Bug is a defect of the intended function of the ) -[//]: # ( product. If you are formally suggesting a change to the behavior of ) -[//]: # ( the product, use the Enhancement form; otherwise, use the Idea form. ) -[//]: # ( Bugs are formal issues. Please fill the form out completely. ) -[//]: # ( ) -[//]: # ( Instructions: ) -[//]: # ( * Title ) -[//]: # ( - Use a short declarative sentence that explaint the defective ) -[//]: # ( behavior. It should be under 120 characters in length. ) -[//]: # ( - End with the emoji sequence: `🐞` `` `🎁` ) -[//]: # ( * Type ) -[//]: # ( - Use the commit types most closely related to your question. For ) -[//]: # ( bugs the commit type is usually `fix`. ) -[//]: # ( * Defect version ) -[//]: # ( - Specify the semver version of the project that is exhibiting the ) -[//]: # ( incorrect behavior. ) -[//]: # ( * Severity ) -[//]: # ( - Use one of: ) -[//]: # ( - 0 Corrupts Data ) -[//]: # ( - 1 Crashes Product ) -[//]: # ( - 2 Blocks Functionality ) -[//]: # ( - 3 Incorrect Behavior ) -[//]: # ( - 4 Incorrect Display ) -[//]: # ( - 5 Documentation Error ) -[//]: # ( - 6 Cosmetic Defect ) -[//]: # ( * Bug description ) -[//]: # ( - Feel free to add formatting. For log dumps and other large ) -[//]: # ( data, attach documents. ) -[//]: # ( * Steps to reproduce ) -[//]: # ( - Provide a repeatable sequence of steps that reproduce the ) -[//]: # ( defect. At the end describe how the tester can verify that the ) -[//]: # ( bug has been reproduced. If you can't reproduce the bug ) -[//]: # ( reliably, submit an Idea instead using the bug form details. ) -[//]: # ( * Desired behavior ) -[//]: # ( - Explain how you think the product ought to operate. ) + +[//]: # ( ############################################################ ) +[//]: # ( # FORM: Bug ) # Bug `` ## Details -| Version | Severity | +| Version | Severity | |:-|:-| -| | | +| | | ## Description -## Steps to reproduce +## Steps to Reproduce + 1. First, ... 1. Next, ... -### Defective behavior - -## Desired behavior - -[//]: # ( ) -[//]: # ( ) -[//]: # ( ### ENHANCEMENT ▢️️ :arrow_forward: ) -[//]: # ( ) -[//]: # ( The Enhancement form should only be used by core members; others ) -[//]: # ( should use the Idea form. An Enhancement represents a change to the ) -[//]: # ( product. Every change must be formally introduced as an Enhancement ) -[//]: # ( issue before a pull request can be submitted. ) -[//]: # ( ) -[//]: # ( Instructions: ) -[//]: # ( * Title ) -[//]: # ( - Use a very short imperative verb phrase since the title is ) -[//]: # ( used in the feature branch for the issue. ) -[//]: # ( - End with the emoji sequence: `▢️️` `` `🎁` ) -[//]: # ( * Definition ) -[//]: # ( - Use "In order to, As a, I want to" format for new features. For ) -[//]: # ( changes to existing features, include the "Whereas" clause. If ) -[//]: # ( the format is too cumbersome, Start with "In order to" but ) -[//]: # ( include a different subsequent clause that somehow includes a ) -[//]: # ( Jali product role an an action performed. ) -[//]: # ( * Tasks ) -[//]: # ( - Use a markdown task list to itemize the development tasks that ) -[//]: # ( contribute toward completion of the Enhancement. ) -[//]: # ( * Acceptance criteria ) -[//]: # ( - Include a task list of detailed tests that will be performed to ) -[//]: # ( verify that the Enhancement works. ) +## Defective Behavior + + +## Desired Behavior + + +[//]: # ( ############################################################ ) +[//]: # ( # FORM: Enhancement ) # Enhancement `` ## Definition @@ -180,14 +62,19 @@ As a ..., I want to ...(, Whereas currently ...). -[//]: # ( ) ## Tasks + - [ ] Task 1. - [ ] Task 2. ## Acceptance Criteria + - [ ] Criterion 1. - [ ] Criterion 2. -[jali]: https://github. +[//]: # ( ############################################################ ) +[//]: # ( # FORM: Epic ) +# Epic `epic-type-name` + +## Description diff --git a/.gitignore b/.gitignore index a2274e6..5c79ae0 100644 --- a/.gitignore +++ b/.gitignore @@ -100,10 +100,13 @@ build/Release node_modules jspm_packages # (Angular CLI) -typings +# typings + +# TypeScript +.Trash* # Optional npm cache directory .npm # Optional REPL history -.node_repl_history \ No newline at end of file +.node_repl_history diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..ab45453 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,19 @@ +{ + "default": true, + "header-style": { "style": "atx" }, + "ul-style": { "style": "dash" }, + "ul-indent": { "indent": 2 }, + "line-length": { + "code_blocks": false, + "line_length": 72, + "tables": false + }, + "ol-prefix": { "style": "one" }, + "list-marker-space": { + "ol_multi": 1, + "ol_single": 1, + "ul_multi": 1, + "ul_single": 1 + }, + "hr-style":{ "style": "---"} +} diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json new file mode 100644 index 0000000..269180b --- /dev/null +++ b/.vscode/cSpell.json @@ -0,0 +1,76 @@ +// cSpell Settings +{ + // Version of the setting file. Always 0.1 + "version": "0.1", + // language - current active spelling language + "language": "en", + // words - list of words to be always considered correct + "words": [ + "apis", + "asarray", + "aspnet", + "berkshelf", + "clavecoder", + "clavecoder's", + "concat", + "cordova", + "ctor", + "destructuring", + "dgeni", + "dist", + "distros", + "dotnet", + "devtool", + "ecma", + "esdoc", + "falsy", + "gitignore", + "golang", + "hypervisor", + "initializations", + "invariants", + "iterability", + "iterable", + "iterables", + "jali", + "jalidev", + "markdownlint", + "metaproperty", + "mkdirp", + "microservice", + "microservices", + "monorepo", + "monorepos", + "multitenant", + "npmignore", + "onboarding", + "partitionable", + "pluggable", + "polyfill", + "provisioner", + "provisioners", + "relavent", + "repo", + "semver", + "serverless", + "shortcode", + "simd", + "simlinks", + "srcs", + "submodule", + "transpilation", + "triaged", + "toolset", + "typeguard", + "truthy", + "vagrantfile", + "validators", + "webpackfile" + ], + // flagWords - list of words to be always considered incorrect + // This is useful for offensive words and common spelling errors. + // For example "hte" should be "the" + "flagWords": [ + "hte" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..56ebb91 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,110 @@ +{ + // https://code.visualstudio.com/docs/runtimes/nodejs + // https://code.visualstudio.com/docs/editor/debugging + "version": "0.2.0", + "configurations": [ + { + "name": "Docs", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/.bin/esdoc", + "stopOnEntry": false, + "args": [ + "-c", + "esdoc.json" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "", + "env": { + "BABEL_ENV": "development" + }, + "runtimeExecutable": null + }, + { + "name": "Examples", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/dist/examples/examples/index.js", + "stopOnEntry": false, + "args": [ + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "", + "env": { + "BABEL_ENV": "development", + "NODE_PATH": "$NODE_PATH:./dist/examples/packages" + }, + "runtimeExecutable": null + }, + // http://stackoverflow.com/a/37064253/2240669 + { + "name": "Remote", + "type": "node", + "request": "attach", + "port": 5858, + "address": "localhost", + "restart": false, + "sourceMaps": false, + "outDir": null, + "localRoot": "${workspaceRoot}", + "remoteRoot": "/vagrant" + }, + { + "name": "Unit Tests", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/.bin/ava", + "stopOnEntry": false, + "args": [ + //"./dist/all/**/${fileBasename}.js", + "--serial", + "--tap" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "build:test", + "env": {"BABEL_ENV": "test"}, + "runtimeExecutable": null + }, + { + "name": "Launch", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/server", + "stopOnEntry": false, + "args": [], + "cwd": "${workspaceRoot}", + "preLaunchTask": "build", + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "externalConsole": false, + "sourceMaps": false, + "outDir": null + }, + { + "name": "Attach", + "type": "node", + "request": "attach", + "port": 5858, + "address": "localhost", + "restart": false, + "sourceMaps": false, + "outDir": null, + "localRoot": "${workspaceRoot}", + "remoteRoot": null + }, + { + "name": "Attach to Process", + "type": "node", + "request": "attach", + "processId": "${command.PickProcess}", + "port": 5858, + "sourceMaps": false, + "outDir": null + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a35451d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.tabSize": 2, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.DS_Store": true + }, + "files.eol": "\n", + //enabled through .editorconfig + // "files.trimTrailingWhitespace": true, + "files.encoding": "utf8", + "git.enabled": true, + "git.enableLongCommitWarning": true, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..2c7666c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,49 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "npm", + "isShellCommand": true, + "showOutput": "always", + "suppressTaskName": true, + "tasks": [ + { + "taskName": "build", + "args": ["run", "build"], + "isBuildCommand": true, + "isWatching": false, + "problemMatcher": [ + "$tsc" + ] + }, + { + "taskName": "build:test", + "args": ["run", "build:test"], + "isBuildCommand": true, + "isWatching": false, + "problemMatcher": [ + "$tsc" + ] + }, + // http://shripalsoni.com/blog/configure-eslint-in-visual-studio-code/ + { + "taskName": "lint", + "args": ["run", "build"], + "problemMatcher": [ + "$eslint-stylish" + ] + }, + { + "taskName": "install", + "args": ["install"] + }, + { + "taskName": "update", + "args": ["update"] + }, + { + "taskName": "test", + "args": ["run", "test"] + } + ] +} \ No newline at end of file diff --git a/Berksfile b/Berksfile index 38b5764..057f845 100644 --- a/Berksfile +++ b/Berksfile @@ -1,3 +1,3 @@ source 'https://supermarket.chef.io' -cookbook 'main', path: './site-cookbooks/main' \ No newline at end of file +cookbook 'main', '~> 0.1.1', path: './site-cookbooks/main' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55cfbc9..d143ed7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,127 +1,197 @@ # Developing Jali -Provides contribution requirements, contribution guidelines, and onboarding information. -## Onboarding +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) + + +Provides contribution requirements, contribution guidelines, and +onboarding information. + + + +> ## Table Of Contents +> +> - [Getting Started](#getting-started) +> - [Prerequisites](#prerequisites) +> - [Prerequisites for NodeJS users](#prerequisites-for-nodejs-users) +> - [Setup](#setup) +> - [Contribution Requirements](#contribution-requirements) +> - [Contribution Guidelines](#contribution-guidelines) +> - [Triage](#triage) +> - [Emoji](#emoji) +> - [Commit message guidelines](#commit-message-guidelines) +> - [Issue states](#issue-states) +> - [Pull Request type](#pull-request-type) +> - [Pull Request states](#pull-request-states) + + + +This document explains how to contribute to the Jali project. For an +introduction to Jali, see [README.md](./README.md). To understand the +design of the Jali project, see [DESIGN.md](./DESIGN.md). + +## Getting Started + ### Prerequisites -1. Install Oracle VirtualBox -1. Install Vagrant + +1. Install [**Oracle VirtualBox**](https://www.virtualbox.org/wiki/Downloads) +1. Install [**Vagrant**](https://www.vagrantup.com/downloads.html) 1. Install and configure Chef: - 1. Install __ChefDK__, [here](https://downloads.chef.io/chef-dk/) - 1. Install the __Chef Vagrant-Omnibus__ plugin + 1. Install [**ChefDK**](https://downloads.chef.io/chef-dk/) + + - For Windows 10, use the Windows 2012r2 x68_64 download + + 1. Install the Chef **vagrant-omnibus** Vagrant plugin > `vagrant plugin install vagrant-omnibus` - 1. Install the __Vagrant-Berkshelf__ plugin - > `vagrant plugin install vagrant-berkshelf` + 1. Install the **vagrant-berkshelf** Vagrant plugin + > `vagrant plugin install vagrant-berkshelf` -#### Note to NodeJS users -> On __Windows 10__ you need to be a part of the `Administrators` group and -> always run `vagrant` from a console as administrator. You possibly can add the -> `SeCreateSymbolicLinkPrivilege` to your account. However your account can't -> "look" like an admin account or will get a stripped down Windows security -> token like administrators do but can't "run as administrator". You would -> have to either disable User Account Control (UAC) or make sure your account -> does not have any of the restricted priviliges. See article `Windows Vista -> Application Development Requirements for User Account Control Compatibility` -> section [New Technologies for Windows][vistauac_topic3] subsection `Access -> Token Changes` for more information and a list of restricted privileges. -> [HT](http://superuser.com/a/839608) -> +#### Prerequisites for NodeJS users + + +> On **Windows 10** you need to be a part of the `Administrators` group +> and always run `vagrant` from a console as administrator. You possibly +> can add the `SeCreateSymbolicLinkPrivilege` to your account. However +> your account can't "look" like an admin account or will get a stripped +> down Windows security token like administrators do but can't "run as +> administrator". You would have to either disable User Account Control +> (UAC) or make sure your account does not have any of the restricted +> privileges. See article `Windows Vista Application Development +> Requirements for User Account Control Compatibility` section +> [New Technologies for Windows][vistauac_topic3] subsection `Access +> Token Changes` for more information and a list of restricted +> privileges. [HT](http://superuser.com/a/839608) +> +> This restriction +> [should soon be lifted](https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/#a5WafruZLjxRYvpW.97). +> > To add privileges to create simlinks: > -> 1. Open a windows security policy editor -> * On __Windows 10 Professional or Enterprise__ open `secpol.msc` -> * On __Windows 10 Home__ download `polsedit` from [here](http://www.southsoftware.com/) -> and open `polseditx64.exe` -> 2. Add your user to `SeCreateSymbolicLinkPrivilege` +> 1. Open a windows security policy editor +> - On **Windows 10 Professional or Enterprise** open `secpol.msc` +> - On **Windows 10 Home** download `polsedit` from +> [here](http://www.southsoftware.com/) +> and open `polseditx64.exe` +> 1. Add your user to `SeCreateSymbolicLinkPrivilege` + + ### Setup -1. Clone `jali` +1. Clone `jali` > `git clone https://github.com/latticework/jali.git` -1. Open a console window (perhaps as Administrator), cd to the project folder - and run Vagrant - +1. Open a console window (perhaps as Administrator), cd to the project + folder and run Vagrant > `vagrant up` -1. Wait for Vagrant and Chef initializations to complete before using new the +1. Wait for Vagrant and Chef initializations to complete before using the virtual machine. -1. In the jali VM, cd to `/vagrant` and start developing. - -#### Note to Visual Studio Code users -> Visual Studio Code has a display issue on Ubuntu 14.04. Use the following -> instructions to get code to display correctly. -> -> 1. For command line execution, `code`, the fix has already been applied. -> 1. Edit /usr/share/applications/code.desktop -> 1. On the following lines, add `--disable-gpu` to the command line -> - `[Desktop Entry]Exec` -> - `[Desktop Action new-window]Exec` +1. In the jali VM, cd to `/vagrant` and start developing or use + **vagrant ssh** + > `vagrant ssh` +1. Initialize `npm`. + > `npm install` -[vistauac_topic3]: https://msdn.microsoft.com/en-us/library/bb530410.aspx#vistauac_topic3 - +1. Build Jali. + > `npm test` ## Contribution Requirements +To contribute you must sign the +[Jali Contributor License Agreement](https://cla-assistant.io/latticework/jali) + ## Contribution Guidelines +### Creating issues + +For `non-contributors` and `contributors`, Jali issues consist of +`Questions` and `Ideas`. Only a `core-contributor` should create a +`Bug`, `Enhancement`, or `Epic`. Please follow +[ISSUE-TEMPLATE-INSTRUCTIONS.md](./ISSUE-TEMPLATE-INSTRUCTIONS.md) when +creating a Jali issue. It provides detailed instructions and a +systematic [decision tree](./ISSUE-TEMPLATE-INSTRUCTIONS.md#template-form-decision-tree) +to determine which issue form you should create. + ### Triage -A core contributor will triage **Question** or **Idea** issues. A -**Question** is triaged by either suggesting the user post the question on -StackOverflow or by answering the question. The question may result in the -creation of **Bug** or **Enhancement** issues; or it may simply be closed. +A `core-contributor` will triage **Question** or **Idea** issues. A +**Question** is triaged by either suggesting the user post the question +on StackOverflow or by answering the question. The question may result +in the creation of **Bug** or **Enhancement** issues; or it may simply +be closed. -An **Idea** is triaged by either putting it in the **Icebox** ZenHub pipeline -until more points accumulate or by the creation of **Bug** or **Enhancement** -issues. +An **Idea** is triaged by either putting it in the **Icebox** ZenHub +pipeline until more points accumulate or by the creation of **Bug** or +**Enhancement** issues. It also may simply be closed. -A **Bug** and **Enhancement** issue is triaged by moving it to the -**Icebox** or **Backlog** ZenHub pipeline or by closing it. +A **Bug** or **Enhancement** issue is triaged by moving it to the +**Icebox** or **Backlog** ZenHub pipeline or by closing it. ### Emoji -Jali projects embrace the use of emoji in GitHub to facilitate communication. -The emoji sets are used to identify issue type, commit type, and issue status. -These emoji are placed at the end of the issue title in the order Issue Type, -Commit Type, Issue Status separated by a single space. Until the issue is -triaged only the issue type should be included. If possible, always use the -Unicode symbol. + +The Jali project embraces the use of emoji in GitHub to facilitate +communication. The emoji sets are used to identify issue type, commit +type, and issue status. These emoji are placed at the end of the issue +title in the order Issue Type, Commit Type, Issue Status separated by a +single space. Until the issue is triaged only the issue type should be +included. If possible, always use the Unicode symbol. + +> Note: In the future, the emoji will be maintained automatically using +> a bot such as [mary-poppins]. ### Commit message guidelines -Use the proper commit type emoji. Allowed commit types and the corresponding -emoji. Use the Unicode character if you can. You can copy the actual Unicode -character by viewing the raw version of this markdown document. + +Use the proper commit type emoji. Allowed commit types and the +corresponding emoji are listed below. Use the Unicode character, if +possible, rather than the GitHub shortcode. You can copy the actual +Unicode character by viewing the raw version of this markdown document. + +### Commit types | Commit Type Code | Unicode Emoji | GitHub Shortcode | |:--|:-:|:--| -| feat | ✨ | `:sparkles:` | -| fix | πŸ”§ | `:wrench:` | -| docs | πŸ“„ | `:page_facing_up:` | -| style | πŸ’„ | `:lipstick:` | +| feat | ✨ | `:sparkles:` | +| fix | πŸ”§ | `:wrench:` | +| docs | πŸ“„ | `:page_facing_up:` | +| style | πŸ’„ | `:lipstick:` | | refactor | πŸ“ | `:triangular_ruler:` | -| perf | πŸƒ | `:running:` | -| test | πŸ”¬ | `:microscope:` | -| chore | πŸ”¨ | `:hammer:` | +| perf | πŸƒ | `:running:` | +| test | πŸ”¬ | `:microscope:` | +| chore | πŸ”¨ | `:hammer:` | ### Issue states + | Issue State | Unicode Emoji | GitHub Shortcode | -|:--|:-:|:--| -| New | 🎁 | `:gift:` | -| Icebox | πŸ’€ | `:zzz:` | -| Backlog | ☰ | N/A | +|:-|:-:|:-| +| New | 🎁 | `:gift:` | +| Icebox | πŸ’€ | `:zzz:` | +| Backlog | ☰ | N/A | | In Progress | 🚢 | `:walking:` | -| Review/QA | βš– | N/A | -| Done | β˜‘οΈ |`:ballot_box_with_check:` | -| Closed | TBD | TBD | +| Review/QA | βš– | N/A | +| Done | β˜‘οΈ |`:ballot_box_with_check:` | +| Closed | TBD | TBD | + +### Pull Request type -### Pull Request states | PR State | Unicode Emoji | GitHub Shortcode | |:--|:-:|:--| -| New | 🎁 | `:gift:` | +| fast-forward | πŸ”ƒ | `:arrows_clockwise:` | +| merge | πŸ”€ | `:twisted_rightwards_arrows:` | +| revert | πŸ”„ | `:arrows_counterclockwise:` | + +### Pull Request states + +| PR State | Unicode Emoji | GitHub Shortcode | +|:-|:-:|:-| +| New | 🎁 | `:gift:` | | Review/QA | βš– | N/A | -| Merged | πŸ’‹ | `:kiss:` | -| Closed | 🚫 | `:no_entry_sign:` | +| Merged | πŸ’‹ | `:kiss:` | +| Closed | 🚫 | `:no_entry_sign:` | -[StackOverflow]: http://stackoverflow.com/questions/tagged/jali \ No newline at end of file +[mary-poppins]: https://github.com/btford/mary-poppins +[StackOverflow]: http://stackoverflow.com/questions/tagged/jali +[vistauac_topic3]: https://msdn.microsoft.com/en-us/library/bb530410.aspx#vistauac_topic3 diff --git a/CREDITS.md b/CREDITS.md index d3c3d33..77c5631 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,19 +1,68 @@ # Jali design credits +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) + + Sources for `Jali` project design ## Development Environment -- Provided by [https://github.com/latticework/jalidev]. + +- Provided by [jalidev]. ## Layout -- Module compiling modeled on [https://github.com/angular/angular]. + +- Module compiling modeled on the [Angular2] repo. + +## Project Management + +- Provided by [ZenHub]. + +## JavaScript Language + +- Decorators + - [TypeScript Handbook][DecoratorTypeScript] + - [Decorators & metadata reflection in TypeScript: From Novice to Expert][DecoratorExpert] + +## NodeJs + +- Express + TypeScript + - [Starter-Kit Node.js Express 4.x app written in TypeScript][ExpressTypeScript] ## Tooling -- webpack2: - - [What's new in webpack 2](https://gist.github.com/sokra/27b24881210b56bbaff7) - - http://blog.waffle.io/code-splitting-angular-2-webpack-2/ -- webpack + ng2: See https://github.com/AngularClass/angular2-webpack-starter. -- webpack + typescript + babel: See [http://blog.johnnyreilly.com/2015/12/es6-typescript-babel-react-flux-karma.html] -- webpack + electron + cordova: See [https://github.com/zalmoxisus/crossbuilder] -- webpack + es6: - - [http://www.2ality.com/2015/12/webpack-tree-shaking.html] - - [http://moduscreate.com/webpack-2-tree-shaking-configuration/] \ No newline at end of file + +- ava: + - [Isomorphic TypeScript, fetch, promises, ava and coverage][ava-example] +- webpack2: + - [What's new in webpack 2][webpack2] + - [Code Splitting in Angular 2][code-splitting] +- webpack + ng2: See [Angular 2 Starter][Angular2Starter]. +- webpack + TypeScript + babel + - Uses [awesome-typescript-loader] + - See [ES6 + TypeScript + Babel + React + Flux + Karma: The Secret Recipe][john-reilly] +- webpack + electron + cordova: See [CrossBuilder] +- webpack + es6: + - [Tree-shaking with webpack 2 and Babel 6][2ality] + - [Webpack 2 Tree Shaking Configuration][modus-create] +- webpack + ng2 + TypeScript + Dgeni: See [angular2-dgeni-starter] +- linting + - [alex]: Catch insensitive, inconsiderate writing + + + +[2ality]: http://www.2ality.com/2015/12/webpack-tree-shaking.html +[alex]: http://alexjs.com/ +[Angular2]: https://github.com/angular/angular +[Angular2Starter]: https://github.com/AngularClass/angular2-webpack-starter "An Angular 2 Starter kit featuring Angular 2 (Router, Http, Forms, Services, Tests, E2E), Karma, Protractor, Jasmine, TypeScript, and Webpack by @AngularClass" +[angular2-dgeni-starter]: https://github.com/rangle/angular2-dgeni-starter +[awesome-typescript-loader]: https://www.npmjs.com/package/awesome-typescript-loader +[ava-example]: http://source.coveo.com/2016/05/11/isomorphic-typescript-ava-w-coverage/ +[code-splitting]: http://blog.waffle.io/code-splitting-angular-2-webpack-2/ +[CrossBuilder]: https://github.com/zalmoxisus/crossbuilder +[DecoratorExpert]: http://blog.wolksoftware.com/decorators-reflection-javascript-typescript +[DecoratorTypeScript]: https://www.typescriptlang.org/docs/handbook/decorators.html (Decorators) +[ExpressTypeScript]: https://github.com/czechboy0/Express-4x-Typescript-Sample +[jalidev]: https://github.com/latticework/jalidev (Linux development environment for Jali projects.) +[john-reilly]: http://blog.johnnyreilly.com/2015/12/es6-typescript-babel-react-flux-karma.html +[modus-create]: http://moduscreate.com/webpack-2-tree-shaking-configuration/ +[webpack2]: https://gist.github.com/sokra/27b24881210b56bbaff7 +[ZenHub]: https://www.zenhub.com/ (ZenHub is agile project management integrated natively in GitHub) diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..e79be41 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,199 @@ +# Jali Repository Design + +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) + +The **Jali** repository is designed to provide a uniform development +and execution environment for polyglot development of Jali packages +and programs within different OS Platforms, such as Windows, Mac, and +Linux distros. + + + +> ## Table Of Contents +> +> - [Development philosophy](#jali-development-philosophy) +> - [Bootstrapping](#bootstrapping) +> - [Modules](#modules) +> - [Extensions](#extensions) +> - [Routines and Execution Context](#routines-and-execution-context) +> - [Jali GitHub repository](#jali-github-repository) +> - [Monorepo](#monorepo) +> - [Security and management](#security-and-management) +> - [Development host configuration](#development-host-configuration) +> - [Vagrant configuration](#vagrant-configuration) +> - Development guest configuration +> - Chef +> - NodeJs +> - Sphinx +> - Editors +> - Visual Studio Code configuration +> - Project configuration +> - Folder Structure +> - `npm` project configuration +> - TypeScript configuration +> - `esdoc` configuration +> - Linters +> - `tslint` configuration +> - `eslint` configuration +> - `markdownlint` configuration +> - Babel configuration +> - `webpack` configuration +> - Editor configuration +> - Visual Studio Code configuration +> - Jali build process + + + +## Introduction + +To set up your Jali development environment, follow the +[Getting Started](./CONTRIBUTING.md#getting-started) section of +**CONTRIBUTING.md**. + +However to understand the design of the Jali repo, we +will discuss the following topics: + +- [Development philosophy](#jali-development-philosophy) +- [Jali GitHub repository](#jali-github-repository) +- [Development host configuration](#development-host-configuration) +- Development guest configuration +- Jali build process + +## Jali Development Philosophy + +Jali provides a consistent, integrated Linux-based development +and operating environment for microservices. The goal is to have a +single, modular tool that provides the full DevOps stack with a +single tool-set. The toolset provides abstracted commands that is +implemented by plug-ins for different technologies and languages. + +The key for Jail is to bring the DevOps process up to a +semantic level to democratize access to microservices, regardless of +chosen language, platform, configuration management tool, cloud +provider, deployment orchestrator, or monitoring toolset. + +It does not necessarily provide interchangeable access to every +technology but allows the project team to pick or migrate to +technologies that may be different from other teams but maintain the +same toolset and microservice configuration and support. + +Initial implementation will be for JavaScript and golang, with support +for dotnet and others to follow. + +### Bootstrapping + +A key design goal of Jali is build and run Jali development with Jali +itself. This may require some creative configuration in the Jali +project but will ensure that soundness of the Jali toolset. + +### Modules + +Another important design goal of Jali is polyglot modularity. To make +the module system as broadly applicable as possible, +[stdio](http://man7.org/linux/man-pages/man3/stdio.3.html) will be used +for all but trusted, i.e., internal, modules. + +In the spirit of [bootstrapping](#bootstrapping), the goal is to have +as much of the native Jali functionality implemented as modules and +extensions. Which leads us to the next goal. + +### Extensions + +A final design goal is to have a well defined extension interface that +hopefully shares the activation/invocation mechanism with +[modules](#/modules). That would include internal cross cutting concerns +such as internal logging, etc. + +### Routines and Execution Context + +The core design goal, however, is the concepts of the Routine and +the Execution Context. + +#### routines + +As much as possible, Jali itself executes using the smallest unit of +execution used by Jali microservices: the **Routine** +(or, at a higher resolution, the **Routine Stage**). Routines are +invoked for a relatively short amount of time, defining an +execution configuration that enables Jali to manage it's execution +environment, scaling and partitioning, and lifetime. In addition, +a Jali Routine operates within a clearly defined set of execution +services, called the Execution Context. + +#### execution context + +In response to receiving a description of a routine's function by way of +its configuration, the Jali kernel presents a routine with an explicitly +defined **Execution Context** that provides microservice-relevant +context and services to the routine. These services include + +- Runtime services +- Consistent Distributed Configuration +- Authentication, Authorization, and Accounting (AAA) +- Exception Processing +- Logging +- Metrics Processing +- Data Context/Caching + +## Jali GitHub repository + +### Monorepo + +The [Jali GitHub repository](https://github.com/latticework/jali) is +a Monolithic Version Control repository or +[**monorepo**](http://danluu.com/monorepo/). This means that the +repository support many interrelated packages together rather than +existing as separate repositories, greatly simplifying development +and package authoring. Currently is supports JavaScript and NPM +packages. In the future it will support polyglot development with +golang and other platforms hosted in the same repo. + +> You can see the packages in the +> [packages/@jali](https://github.com/latticework/jali/tree/master/packages/@jali) +> folder. + +### Security and management + +The repo is managed by core contributors who are members of the +**Latticework** research group and is updated using GitHub Pull Requests. + +Project management utilizes [ZenHub](https://www.zenhub.com/) in a manner +specified in the [Contribution Guidelines](./CONTRIBUTING.md#contribution-guidelines) +section of **CONTRIBUTING.md**. The project uses GitHub +[issues](https://github.com/latticework/jali/issues) for as ZenHub Epics +and User Stories. Work item state is managed by using GitHub +[labels](https://github.com/latticework/jali/labels). The contribution +guidelines specifies the use of emoji to facilitate work item status +awareness. This feature may be automated in the future. + +Development progress is tracked using a +[ZenHub KanBan board](https://github.com/latticework/jali/pulls#boards?repos=45436564) +and GitHub [milestones](https://github.com/latticework/jali/milestones). + +Currently feature branches are made directly from **master**, though a +**dev** branch may be introduced as the product matures. + +## Development Host Configuration + +The goal is for Jali to support development hosts for Windows, Mac and +various Linux flavors. The goal is to have the host configuration +itself configuration driven with a **jali.json** configuration pattern. + +Currently however Vagrant and ChefDK are required in addition to git. +Lets look how these work together to build the Jali dev environment. + +### Vagrant configuration + +As mentioned in the [Getting Started](./CONTRIBUTING.md#getting-started) +section of **CONTRIBUTING.md**, Jali integrates Vagrant with Chef using +the vagrant plugins +[vagrant-omnibus](https://github.com/chef/vagrant-omnibus) and +[vagrant-berkshelf](https://github.com/berkshelf/vagrant-berkshelf). + +Respectively, these plugins install Chef into the vagrant guest machine +and execute Berkshelf files on those guest machines. + +#### Vagrantfile layout + +The [Vagrantfile](./Vagrantfile) diff --git a/ISSUE-TEMPLATE-INSTRUCTIONS.md b/ISSUE-TEMPLATE-INSTRUCTIONS.md new file mode 100644 index 0000000..9b06161 --- /dev/null +++ b/ISSUE-TEMPLATE-INSTRUCTIONS.md @@ -0,0 +1,303 @@ +# [Jali GitHub Issue Template][jali-issue-template] Instructions + + + + +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) +[//]: # (Note: Comment format explained by: ) +[//]: # (http://stackoverflow.com/a/32190021 ) + +This template supports five types of issues: **Question**, **Idea**, +**Bug**, **Enhancement**, and **Epic**. Review [Β§ General Instructions](#general-instructions), +then use the [Β§ Template Form Decision Tree](#template-form-decision-tree) +to chose the correct form. Please fill out the appropriate template and +remove others. + +> ## Table Of Contents +> - [General Instructions](#general-instructions) +> - [Issue workflow](#issue-workflow) +> - [Commit types](#commit-types) +> - [Template Form Decision Tree](#template-form-decision-tree) +> - [Template Forms](#template-forms) +> - [Question](#question--question) +> - [Idea](#idea--bulb) +> - [Bug](#bug--beetle) +> - [Enhancement](#enhancement--arrow_forward) +> - [Epic](#epic--movie_camera) + +## General Instructions + +### Issue workflow + +The Jali repository uses [ZenHub][zen-hub] for project management. See +[CONTRIBUTING.md] for the proper workflow for Jali GitHub issues. If +you are a `non-contributor`, please enter your issue using the +`Question` or `Idea` form. See the [Β§ Template Form Decision Tree](#template-form-decision-tree) +for exact instructions. + +### Commit types + +An issue can be focused by specifying what kind of commit is envisioned +by a change. If more than one type of commit is associated with the +issue, consider how you can break it up into multiple issues. + +> **Important:** See +> [CONTRIBUTING.md Β§ Commit types][commit-types] for list of commit types +> and their emoji character representations. + +## Template Form Decision Tree + +Use the following decision tree to choose the appropriate form: + +1. Are you a `non-contributor` or a `contributor`? If `non-contributor`, + go to **(A)**; if `contributor`, go to **(D)**. +1. **(A)** You are a `non-contributor`. Are you suggesting a change to + current documentation, implementation, or behavior of the Jali + developer experience or system operation? If yes, go to **(C)**; + otherwise, go to **(B)**. +1. **(B)** You are a `non-contributor` asking a question. Enter any bug + reports also as a question. Go to **(J)**. +1. **(C)** You are a `non-contributor` wanting an enhancement. Is the + change really just a defect from documented functionality? If yes, go + to **(B)**. Otherwise, are you quite certain the desired change or + addition does not exist? If yes, go to **(K)**; otherwise, go to **(B)**. +1. **(D)** You are a `contributor`. If you have a question, go to + **(E)**, if a bug report, go to **(F)**; otherwise, go to **(H)**. +1. **(E)** You are a `contributor` asking a question. Go to **(J)**. +1. **(F)** Do you have enough details to complete the `Bug` form? If + yes, go to **(G)**; otherwise, go to **(J)**. +1. **(G)** You are a `contributor` entering a bug report. If you are a + `core-contributor` go to **(L)**; otherwise, go to **(J)**. +1. **(H)** You are a `contributor` wanting an enhancement. Do you you + have enough information to fill out the `Enhancement` form? If yes, + go to **(I)**; otherwise, go to **(K)**. +1. **(I)** Are you a `core-contributor` that needs to create an `Epic`? + If yes, go to **(N)**; otherwise, if you are a `core-contributor`, go + to **(M)**. If not, go to **(K)**. +1. **(J)** Check on [StackOverflow][stack-overflow-jali] if the question + has been asked already. If not, and it meets StackOverflow criteria + for questions, create a StackOverflow question; otherwise, create a + [Question](#question-❓-question). + ❌ +1. **(K)** Create an [Idea](#idea--bulb). ❌ +1. **(L)** Create a [Bug](#bug--beetle). ❌ +1. **(M)** Create an [Enhancement](#enhancement--arrow_forward). ❌ +1. **(N)** Create an [Epic](#epic-movie_camera). ❌ + + +## Template Forms + +### Question ❓ `:question:` + +Usage questions should be asked at [StackOverflow][stack-overflow-jali]. +However, not all questions are appropriate for StackOverflow. See +[Welcome to Stack Overflow](http://stackoverflow.com/tour) for +what kinds of questions are appropriate for StackOverflow. Use the +`jali` tag for questions about Jali. Other questions should be asked in, +this, the `jali` GitHub repo, by creating an issue using the `Question` +form. If you are a non-`core-contributor`, enter a `Bug` using the +`Question` form. A `core-contributor` may create a `Bug` for you. + +#### Question Instructions + +- **Title** + - Use a short interrogative sentence, i.e. a question, that + should be under 120 characters in length. + - End with the emoji sequence: `❓` `<`[commit-type-emoji][commit-types]`>` `🎁` +- **Commit Type** + - Use the commit types most closely related to your question. +- **Question Details** + - Feel free to add formatting and images. For log dumps and other + large data, attach documents. If you have created a PR or are a + non-core contributor, keep the **Question** header but include + the `Bug` form body. + +```markdown +# Question: `` + +## Details + + +``` + +### Idea πŸ’‘ `:bulb:` + +An `Idea` is a suggested change to the system. If you not a +`core-contributor` and intend to submit a GitHub PR, you should submit +an `Idea` issue first, then reference the Idea from the PR. A +`core-contributor` may create a `Bug` or `Enhancement` for you. + +#### Idea Instructions + +- **Title** + - Use a short imperative verb phrase. It should be under 120 + characters in length. + - End with the emoji sequence: `πŸ’‘` `<`[commit-type-emoji][commit-types]`>` `🎁` +- **Commit Type** + - Use the commit types most closely related to your question. +* **Idea details** + - Feel free to add formatting and images. For log dumps and other + large data, attach documents. If you have created a PR or are a + non-core contributor, keep the **Idea** header but include the + the `Enhancement` form body. + +```markdown +# Idea: `` + +## Details + + +``` + +### Bug 🐞 `:beetle:` + +The `Bug` form should only be used by `core-contributors`; others should +use the `Idea` form. A `Bug` is a defect of the intended function of the product. +If you are formally suggesting a change to the behavior of the product, +use the `Enhancement` form; otherwise, use the `Idea` form. Bugs are +formal issues. Please fill the form out completely. + +#### Bug Instructions + +- **Title** + - Use a short declarative sentence that explains the defective + behavior. It should be under 120 characters in length. + - End with the emoji sequence: + `🐞` `<`[commit-type-emoji][commit-types]`>` `🎁` +- **Commit Type** + - Choose the [commit type][commit-types] most closely related to your + bug. For bugs, the commit type is usually `fix`. +- **Defect Version** + - Specify the `semver` version of the project that is exhibiting the + incorrect behavior. +- **Severity** + - Specify the bug's severity. Include the number and name. Use one of: + - `0 Corrupts Data` + - `1 Crashes Product` + - `2 Blocks Functionality` + - `3 Incorrect Behavior` + - `4 Incorrect Display` + - `5 Documentation Error` + - `6 Cosmetic Defect` +- **Bug Description** + - Feel free to add formatting and an image. For many images, details, + or logs, add attachments in the **Defective Behavior** section, + instead. +- **Steps to Reproduce** + - Provide a repeatable sequence of steps that reproduce the defect. At + the end, describe how the tester can verify that the bug has been + reproduced. If you can't reproduce the bug reliably, submit an `Idea` + instead, using the `Bug` form template. +- **Defective Behavior** + - Explain erroneous outcome. Include images and attachments, such as + logs, if possible. +- **Desired Behavior** + - Explain how you think the product ought to operate. Include images, + if possible. + +```markdown +# Bug `` + +## Details + +| Version | Severity | +|:-|:-| +| | | + +## Description + + +## Steps to Reproduce + +1. First, ... +1. Next, ... + +## Defective Behavior + + +## Desired Behavior + + +``` + +### Enhancement ▢️️ `:arrow_forward:` + +The `Enhancement` form should only be used by `core-contributors`; +others should use the `Idea` form. An `Enhancement` represents a change +to the product. Every change must be formally introduced as an +`Enhancement` before a PR can be triaged. + +#### Enhancement Instructions + +- **Title** + - Use a very short imperative verb phrase since the title is used in + the feature branch for the issue. + - End with the emoji sequence: + `▢️️` `<`[commit-type-emoji][commit-types]`>` `🎁` +- **Definition** + - Use "In order to, As a, I want to" format for new features. For ) + changes to existing features, include the "Whereas" clause. If ) + the format is too cumbersome, Start with "In order to" but ) + include a different subsequent clause that somehow includes a ) + Jali product role an an action performed. ) +- **Tasks** + - Use a markdown task list to itemize the development tasks that ) + contribute toward completion of the Enhancement. ) +- **Acceptance criteria** + - Include a task list of detailed tests that will be performed to ) + verify that the Enhancement works. ) + +```markdown +# Enhancement `` + +## Definition +[//]: # ( Format follows http://blog.crisp.se/2014/09/25/david-evans/as-a-i-want-so-that-considered-harmful) +In order to ..., + +As a ..., + +I want to ...(, + +Whereas currently ...). + +## Tasks + +- [ ] Task 1. +- [ ] Task 2. + +## Acceptance Criteria + +- [ ] Criterion 1. +- [ ] Criterion 2. + +``` + +### Epic πŸŽ₯ `:movie_camera:` + +In Jali, a [ZenHub][zen-hub] *Epic* associates related `Enhancement` +issues into a feature requirement category. + +#### Epic Instructions + +- **Title** + - Use a very short noun phrase, such as `User Management`. + - End with the emoji sequence: + `πŸŽ₯` `<`[commit-type-emoji][commit-types]`>` `🎁` +- **Title** + - Clearly, but succinctly, define which would constitute an + `Enhancement` that would fit in this `Epic` and which would not. + +```markdown +# Epic `epic-type-name` + +## Description + + +``` + +[CONTRIBUTING.md]: ./CONTRIBUTING.md +[commit-types]: ./CONTRIBUTING.md#commit-types +[jali-issue-template]: https://github.com/latticework/jali/issues/new +[stack-overflow-jali]: http://stackoverflow.com/questions/tagged/jali +[zen-hub]: https://www.zenhub.com/ diff --git a/README.md b/README.md index 724711c..1ff6b23 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,191 @@ -# jali -Jali execution context. Includes explicit access to support services such as security, configuration, and logging. +# Jali - +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) + + +Specification-driven serverless microservice DevOps framework and +infrastructure [http://jali-ms.io/](http://jali-ms.io/) + + +[//]: # (Add a table of NPM badges. Consider https://badge.fury.io/) + +[![OpenHub stats](https://www.openhub.net/p/jali/widgets/project_thin_badge.gif)](https://www.openhub.net/p/jali) +[![project status](https://img.shields.io/badge/project_status-pre--alpha-AA0114.svg)](https://github.com/latticework/jali/milestones) +[![ZenHub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://github.com/latticework/jali/milestones#boards?repos=45436564&milestones=0.1.0#) +[![Dependency Status](https://dependencyci.com/github/latticework/jali/badge)](https://dependencyci.com/github/latticework/jali) +[![bitHound Overall Score](https://www.bithound.io/github/latticework/jali/badges/score.svg)](https://www.bithound.io/github/latticework/jali) + +[![CLA assistant](https://cla-assistant.io/readme/badge/latticework/jali)](https://cla-assistant.io/latticework/jali) +[![Gitter](https://badges.gitter.im/latticework/jali.svg)](https://gitter.im/latticework/jali?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Build Status](https://semaphoreci.com/api/v1/latticework/jali/branches/master/shields_badge.svg)](https://semaphoreci.com/latticework/jali) + + + +> ## Table of Contents +> +> - [Introduction](#introduction) +> - [Developing Jali](#developing-jali) +> - [Technologies Demonstrated](#technologies-demonstrated) +> - [Development environment](#development-environment) +> - [Cloud development tools](#cloud-development-tools) +> - [NodeJS Development Tools](#nodejs-development-tools) + + + +## Introduction + +### **Jali** is + +- **specification-driven** permitting consumer driven contracts and + multi-version management +- **serverless** so you can write just your routines and run using any + and all configurable platforms +- where the **microservice** is the unit of development, delivery and + management +- a full multitenant, partitionable **DevOps** platform because modern + microservice delivery is continuous +- an extensible, polyglot **framework** providing circuit breaking and + self-documenting APIs and explicit routine services +- an **infrastructure** pluggable for many major microservice platforms + (eventually...) + +To learn more about developing and managing microservices with Jali, + visit our [website]. ## Developing Jali -_See [CONTRIBUTING.md]._ +*See [CONTRIBUTING.md](./CONTRIBUTING.md).* + +## Project Design + +*See [DESIGN.md](./DESIGN.md).* + +## Technologies Demonstrated + +See [CREDITS.md](./CREDITS.md) for instructions or examples how to use +some of these technologies. + +### Development environment + +- [VirtualBox] ([wiki][VirtualBoxWiki]) a free and open-source + hypervisor for x86 computers +- [Vagrant] ([wiki][VagrantWiki]) manages virtual development + environments + - [chef_zero provisioner][chef_zeroProvisioner] allows you to + provision the guest OS using Chef + - [vagrant-omnibus] "ensures the desired version of Chef is installed" + - [vagrant-berkshelf] "plugin to add Berkshelf integration to the Chef + provisioners" +- [Ubuntu] ([wiki][UbuntuWiki]) development platform vagrant guest OS +- [Chef] ([wiki][ChefWiki]) configuration management for Ubuntu + development environment + - [ChefDK] Chef developer tools + - [Berkshelf] Chef cookbook dependency manager + - [chef-zero] "lightweight Chef server that runs in-memory on the + local machine" +- [Visual Studio Code][vscode] ([wiki][vscodeWiki]) cross platform + source code editor + - Extensions + - [vscode-markdownlint] Markdown linting and style checking for Visual + Studio Code + - [code-spell-checker] Spelling checker for source code + - Many more. See `clavecoder's` Visual Studio Code Sync Settings + [clavecoder-extensions] file for a full list of useful Visual + Studio Code extensions. +- [JaliDev] seed project for providing team-consistent development + environments using the + technologies above. + +### Cloud/GitHub development tools + +- ALM + - [ZenHub] GitHub-integrated agile project management +- CI + - [Semaphore]: Make continuous delivery easy +- PR policy checks + - [bitHound]: Node.js code analysis your team can agree on + - [VersionEye]: notifies you about out-dated dependencies, security + vulnerabilities and license violations in your Git repositories + - [Dependency CI][dependency-ci]: Continuously Test Your Dependencies + - [CLA assistant][cla-assistant]: Easily handle Contributor License + Agreements (CLAs) + +### NodeJS Development Tools + +- `monorepo`-style projects + - [Why is Babel a monorepo?][monorepo-babel] + - [New wave modularity with Lerna, monorepos, and npm organizations][monorepo-turf] + - [SETTING UP A JAVASCRIPT MONOREPO][monorepo-cycle] + - [Monorepos in Git][atlassian-monorepo] +- `npm` as a task runner + - [How to Use npm as a Build Tool][keith-cirkel] +- [TypeScript 2.1][TypeScript] ([wiki][TypeScriptWiki]) adds optional + static typing to JavaScript + - [TypeDoc] TypeScript document generator + > Warning: [**TypeDoc** doesn't work with TypeScript 2 yet][TypeDocNotCompatible] + - [tslint] An extensible linter for the TypeScript language +- [EcmaScript 2017+][EcmaScript] ([wiki][EcmaScriptWiki]) The maturing + JavaScript language + - See [ECMASCRIPT-PROPOSALS.md](./ECMASCRIPT-PROPOSALS.md) for what + features and proposals are implemented in Jali + - [esdoc] (integrated by Jali) + - [eslint] The pluggable linting utility for JavaScript and JSX +- [webpack 2][webpack] ([wiki][WebpackWiki]) NodeJS module loader +- [Babel 6][Babel] JavaScript parser and transpilation platform +- [AVA] Concurrent JavaScript test framework for EcmaScript + Babel + - [istanbul] JavaScript code coverage tool + - [nyc] Istanbul CLI +[//]: - (From https://github.com/igrigorik/ga-beacon) +[![Analytics](https://ga-beacon.appspot.com/UA-81234965-2/welcome-page)](https://github.com/igrigorik/ga-beacon) -[CONTRIBUTING.md]: ./CONTRIBUTING.md \ No newline at end of file +[atlassian-monorepo]: https://developer.atlassian.com/blog/2015/10/monorepos-in-git/ +[AVA]: https://github.com/avajs/ava +[Babel]: https://babeljs.io/ +[Berkshelf]:http://berkshelf.com/ +[bitHound]: https://www.bithound.io/ +[Chef]: https://www.chef.io/ +[ChefDK]: https://downloads.chef.io/chef-dk/ +[ChefWiki]: https://en.wikipedia.org/wiki/Chef_(software) +[chef-zero]: https://docs.chef.io/ctl_chef_client.html#run-in-local-mode +[chef_zeroProvisioner]: https://www.vagrantup.com/docs/provisioning/chef_zero.html +[cla-assistant]: https://cla-assistant.io/ +[clavecoder-extensions]: https://gist.github.com/clavecoder/fa29a8846199bee62bc99ef94fbe86df#file-extensions-json +[code-spell-checker]: https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker +[dependency-ci]: https://dependencyci.com/ +[dgeni]: https://github.com/angular/dgeni +[dgeniNotCompatible]: https://github.com/angular/dgeni-packages/issues/193 +[EcmaScript]: https://github.com/tc39/proposals +[EcmaScriptWiki]: https://en.wikipedia.org/wiki/ECMAScript +[esdoc]: https://esdoc.org/ +[eslint]: http://eslint.org/ +[istanbul]: https://github.com/gotwarlost/istanbul +[JaliDev]: https://github.com/latticework/jalidev +[keith-cirkel]: https://www.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/ +[monorepo-babel]: https://github.com/babel/babel/blob/master/doc/design/monorepo.md#why-is-babel-a-monorepo +[monorepo-cycle]: http://staltz.com/setting-up-a-javascript-monorepo.html +[monorepo-turf]: http://www.macwright.org/2016/07/08/lerna-npm-organizations-new-wave-modularity.html +[nyc]: https://github.com/istanbuljs/nyc +[Semaphore]: https://semaphoreci.com/ +[tslint]: https://palantir.github.io/tslint/ +[TypeDoc]: http://typedoc.io/ +[TypeDocNotCompatible]: https://github.com/TypeStrong/typedoc/issues/234 +[TypeScript]: https://blogs.msdn.microsoft.com/typescript/2016/07/11/announcing-typescript-2-0-beta/ +[TypeScriptWiki]: https://en.wikipedia.org/wiki/TypeScript +[Ubuntu]: http://www.ubuntu.com/ +[UbuntuWiki]: https://en.wikipedia.org/wiki/Ubuntu +[Vagrant]: https://www.vagrantup.com/ +[vagrant-berkshelf]:https://github.com/berkshelf/vagrant-berkshelf +[vagrant-omnibus]: https://github.com/chef/vagrant-omnibus +[VagrantWiki]: https://en.wikipedia.org/wiki/Vagrant_(software) +[VersionEye]: https://www.versioneye.com/ +[VirtualBox]: https://www.virtualbox.org/ +[VirtualBoxWiki]: https://en.wikipedia.org/wiki/VirtualBox +[vscode]: https://code.visualstudio.com/ +[vscodeWiki]: https://en.wikipedia.org/wiki/Visual_Studio_Code +[vscode-markdownlint]: https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint +[webpack]: https://gist.github.com/sokra/27b24881210b56bbaff7 +[website]: http://jali-ms.io/ +[WebpackWiki]: https://en.wikipedia.org/wiki/Webpack +[ZenHub]: https://www.zenhub.com/ diff --git a/Vagrantfile b/Vagrantfile index f2749b1..7fb0dc7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -2,20 +2,16 @@ # -*- mode: ruby -*- # vi: set ft=ruby : - # For examples on parameterizing see: # http://stackoverflow.com/questions/13065576/override-vagrant-configuration-settings-locally-per-dev - Vagrant.configure(2) do |config| - # Ubuntu Server 14.04 LTS - config.vm.box = 'box-cutter/ubuntu1404-desktop' - # config.vm.box = 'box-cutter/ubuntu1604-desktop' + # Ubuntu Server 16.04 LTS + config.vm.box = 'box-cutter/ubuntu1604-desktop' config.vm.provider 'virtualbox' do |vb| end - # Install the latest version of Chef. # For more information see https://github.com/chef/vagrant-omnibus # @@ -36,12 +32,14 @@ Vagrant.configure(2) do |config| chef.add_recipe 'main::default' end - # See: http://stackoverflow.com/a/20431791 # Seealso: http://friendsofvagrant.github.io/v1/docs/config/vm/define.html config.vm.define 'jali' do |jali| jali.vm.synced_folder 'C:\git', '/git' + # http://stackoverflow.com/a/37064253/2240669 + jali.vm.network :forwarded_port, host: 5858, guest: 5858 + jali.vm.provider 'virtualbox' do |vb| # For a complete reference, please see the online documentation at # https://docs.vagrantup.com/v2/virtualbox/configuration.html diff --git a/config/dgeni/index.js b/config/dgeni/index.js new file mode 100644 index 0000000..f8c31be --- /dev/null +++ b/config/dgeni/index.js @@ -0,0 +1,96 @@ +var Package = require('dgeni').Package; + +var basePackage =require('dgeni-packages/base') +var jsdocPackage = require('dgeni-packages/jsdoc'); +var typescriptPackage = require ('dgeni-packages/typescript'); +var nunjucksPackage = require('dgeni-packages/nunjucks'); + +var path = require('canonical-path'); + + +// See https://github.com/rangle/angular2-dgeni-starter/blob/master/dgeni-templates/dgeni-package.js +// See https://github.com/ericjim/dgeni-typescript-example + +var SETTINGS = { + ts: { + include: './packages/**/*.ts', + exclude: './packages/**/*.test.ts', + base: './packages', + }, + js: { + include: [ + './dist/all/**/*.js' + ], + exclude: [ + './dist/all/**/*.test.js', + './dist/all/**/testing/**.js' + ], + base: './dist/all', + }, + output: './dist/docs', +} + +module.exports = new Package('jalijs', [ + basePackage, + jsdocPackage, + nunjucksPackage, + typescriptPackage, +]) + +// this is required because the parsing-tags pseudo marker processor is defined in jsdoc package which is not referenced here. +.processor({ name: 'parsing-tags', $runAfter: ['files-read'], $runBefore: ['processing-docs'] }) + + +.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { + + // computePathsProcessor.pathTemplates = []; + computePathsProcessor.pathTemplates.push({ + docTypes: EXPORT_DOC_TYPES, + pathTemplate: '${moduleDoc.path}', + outputPathTemplate: '${name}-${docType}.html', + }); +}) + +.config(function( + log, + readFilesProcessor, + readTypeScriptModules, + templateFinder, + writeFilesProcessor) { + + // Default log level (can be overridden by --log option). + log.level = 'info'; + + // Set base path to project root. + readFilesProcessor.basePath = path.resolve(__dirname, '../..'); + + // Specify collections of source files that should contain the documentation to extract + readFilesProcessor.sourceFiles = [ + { + include: SETTINGS.js.include, + exclude: SETTINGS.js.exclude, + basePath: SETTINGS.js.base + } + ]; + + readTypeScriptModules.sourceFiles = [ + { + include: SETTINGS.ts.include, + exclude: SETTINGS.ts.exclude, + basePath: SETTINGS.ts.base + } + ]; + + readTypeScriptModules.hidePrivateMembers = false; + + + // Add a folder to search for our own templates to use when rendering docs. + templateFinder.templateFolders.unshift(path.resolve(__dirname, 'templates')); + + // Specify how to match docs to templates. + // In this case we just use the same static template for all docs + templateFinder.templatePatterns.unshift('class.template.html'); + + // Output folder + writeFilesProcessor.outputFolder = SETTINGS.output; +}); \ No newline at end of file diff --git a/config/dgeni/templates/class.template.html b/config/dgeni/templates/class.template.html new file mode 100644 index 0000000..c1d4e7c --- /dev/null +++ b/config/dgeni/templates/class.template.html @@ -0,0 +1,36 @@ +

{{ doc.codeName }} ({{ doc.outputPath }})

+

{{ doc.description }}

+ +{%- if doc.params %} +

Params

+
    +{%- for param in doc.params %} +
  • + {{ param.name }} { {{ param.typeList }} } - {{ param.description }} +
  • +{%- endfor %} +
+{%- endif %} + +{%- if doc.returns %} +

Returns

+

+ { {{ doc.returns.typeList }} } - {{ doc.returns.description }} +

+{%- endif %} + +
    +{%- for member in doc.members %} +
  • +
    + method name: {{ member.name }} +
    +
    + method description: {{ member.description }} +
    +
    + method params: {%- for param in member.params %} {{ param.name }} {%- endfor %} +
    +
  • +{%- endfor %} +
\ No newline at end of file diff --git a/docs/esdoc/overview.md b/docs/esdoc/overview.md new file mode 100644 index 0000000..dcc1383 --- /dev/null +++ b/docs/esdoc/overview.md @@ -0,0 +1,154 @@ + + +## Packages + +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) + + + + +|Package|Description|Modules| +|:-|:-|:-| +|[@jali/core](overview.html#package-jali-core)|framework-level utilities|`@jali/core`
`@jali/core/iterables`
`@jali/core/type-guards`| +|[@jali/util](overview.html#package-jali-util)|language-level utilities|`@jali/util`
`@jali/util/errors`
`@jali/util/iterables`
`@jali/util/type-guards`| + + + +## Package @jali/core + +_Back to [Packages](overview.html#packages)_ + +Provides framework-level utilities such as notification messages and +structured errors. + +## Package @jali/util + +_Back to [Packages](overview.html#packages)_ + +Provides language-level utilities such as parameter verification and +common `Iterable` functions. + + + +|Export|Description| +|:-|:-| +|Errors|Re-export of [`@jali/util/errors`](overview.html#module-jali-util-errors)| +|Iterables|Re-export of [`@jali/util/iterables`](overview.html#module-jali-util-iterables)| +|TypeGuards|Re-export of [`@jali/util/type-guards`](overview.html#module-jali-util-type-guards)| + + + +### Module @jali/util/errors + +_Back to [Package @jali/util](overview.html#package-jali-util)_ + + + +|Export|Description| +|:-|:-| +|[ArgumentEmptyStringError]|Represents that an argument erroneously has an empty string value.| +|[ArgumentError]|Represents that an argument has violated a requirement.| +|[ArgumentFalseError]|Represents that an argument erroneously has a value of `false`.| +|[ArgumentFalsyError]|Represents that an argument erroneously has a _falsy_ value.| +|[ArgumentNanError]|Represents that an argument erroneously has a value of `NaN`.| +|[ArgumentNullError]|Represents that an argument erroneously has a value of `null`.| +|[ArgumentTypeError]|Represents that an argument has an invalid type or an object with the incorrect structure.| +|[ArgumentUndefinedError]|Represents that an argument erroneously is 'undefined'.| +|[ArgumentWhitespaceStringError]|Represents that a string argument erroneously has only whitespace characters.| +|[ArgumentZeroError]|Represents that an argument erroneously has a value of zero.| +|[verifyArgument]|Throws an error if the specified argument value does not pass the specified test.| +|[verifyBoolean]|Throws an error if the specified argument is not strictly a boolean value.| +|[verifyDefined]|Throws an error if the specified argument is `undefined`.| +|[verifyFunction]|Throws an error if the specified argument is not strictly a function expression.| +|[verifyIterable]|Throws an error if the specified argument does not support iteration.| +|[verifyNonEmpty]|Throws an error if the specified argument value is not a non-empty string.| +|[verifyNonZero]|Throws an error if the specified argument value is not a non-zero number.| +|[verifyNotNull]|Throws an error if the specified argument value is `undefined` or `null`.| +|[verifyNotWhitespace]|Throws an error if the specified argument is not a string with non whitespace characters.| +|[verifyNumber]|Throws an error if the specified argument value is not a `number` or has a value of `NaN`.| +|[verifyObject]|Throws an error if the specified argument value is not an `Object`.| +|[verifyString]|Throws an error if the specified argument value is not a `string`.| +|[verifyTrue]|Throws an error if the specified argument value is not a boolean with the value 'true'.| +|[verifyTruthy]|Throws an error if the specified argument value is not _truthy_.| + + + +[ArgumentEmptyStringError]: ../class/all/@jali/util/src/argument-empty-string-error.js~ArgumentEmptyStringError.html +[ArgumentError]: ../class/all/@jali/util/src/argument-error.js~ArgumentError.html +[ArgumentFalseError]: ../class/all/@jali/util/src/argument-false-error.js~ArgumentFalseError.html +[ArgumentFalsyError]: ../class/all/@jali/util/src/argument-falsy-error.js~ArgumentFalsyError.html +[ArgumentNanError]: ../class/all/@jali/util/src/argument-nan-error.js~ArgumentNanError.html +[ArgumentNullError]: ../class/all/@jali/util/src/argument-null-error.js~ArgumentNullError.html +[ArgumentTypeError]: ../class/all/@jali/util/src/argument-type-error.js~ArgumentTypeError.html +[ArgumentUndefinedError]: ../class/all/@jali/util/src/argument-undefined-error.js~ArgumentUndefinedError.html +[ArgumentWhitespaceStringError]: ../class/all/@jali/util/src/argument-whitespace-string-error.js~ArgumentWhitespaceStringError.html +[ArgumentZeroError]: ../class/all/@jali/util/src/argument-zero-error.js~ArgumentZeroError.html +[verifyArgument]: ../function/index.html#static-function-verifyArgument +[verifyBoolean]: ../function/index.html#static-function-verifyBoolean +[verifyDefined]: ../function/index.html#static-function-verifyDefined +[verifyFunction]: ../function/index.html#static-function-verifyFunction +[verifyIterable]: ../function/index.html#static-function-verifyIterable +[verifyNonEmpty]: ../function/index.html#static-function-verifyNonEmpty +[verifyNonZero]: ../function/index.html#static-function-verifyNonZero +[verifyNotNull]: ../function/index.html#static-function-verifyNotNull +[verifyNotWhitespace]: ../function/index.html#static-function-verifyNotWhitespace +[verifyNumber]: ../function/index.html#static-function-verifyNumber +[verifyObject]: ../function/index.html#static-function-verifyObject +[verifyString]: ../function/index.html#static-function-verifyString +[verifyTrue]: ../function/index.html#static-function-verifyTrue +[verifyTruthy]: ../function/index.html#static-function-verifyTruthy + +### Module @jali/util/iterables + +_Back to [Package @jali/util](overview.html#package-jali-util)_ + + + +|Export|Description| +|:-|:-| +|[asArray]|Converts an argument that could either be a value of a type or a sequence of that type to
an array of that type.| +|[asIterable]|Converts an argument that could either be a value of a type or a sequence of that type to a
sequence of that type.| +|[concat]|Concatenates a sequence of a type with zero or more other sequences of that type.| +|[every]|Returns a value indicating whether every element fulfills the specified test.| +|[filter]|Returns a subset of the sequence of those elements that pass the specified test.| +|[find]|Returns the first value matching the specified test or `undefined` if no match was found.| +|[includes]|Returns a value indicating whether a match for the specified test was found.| +|[map]|Returns a sequence of elements that are the result of calling the specified converter
function on each element.| +|[reduce]|Aggregates a sequence to a single computed element value.| +|[slice]|Returns a segment of the original sequence.| +|[some]|Returns a value indicating whether any of the elements of a sequence pass the specified test.| +|[toMap]|Converts a sequence to a `Map` using the specified key selector function.| + + + +[asArray]: ../function/index.html#static-function-asArray +[asIterable]: ../function/index.html#static-function-asIterable +[concat]: ../function/index.html#static-function-concat +[every]: ../function/index.html#static-function-every +[filter]: ../function/index.html#static-function-filter +[find]: ../function/index.html#static-function-find +[includes]: ../function/index.html#static-function-includes +[map]: ../function/index.html#static-function-map +[reduce]: ../function/index.html#static-function-reduce +[slice]: ../function/index.html#static-function-slice +[some]: ../function/index.html#static-function-some +[toMap]: ../function/index.html#static-function-toMap + +### Module @jali/util/type-guards + +_Back to [Package @jali/util](overview.html#package-jali-util)_ + + + +|Export|Description| +|:-|:-| +|[isError]|| +|[isIterable]|| +|[makeIsIterable]|| + + + +[isError]: ../function/index.html#static-function-isError +[isIterable]: ../function/index.html#static-function-isIterable +[makeIsIterable]: ../function/index.html#static-function-makeIsIterable diff --git a/ecmascript-proposals.md b/ecmascript-proposals.md new file mode 100644 index 0000000..7085448 --- /dev/null +++ b/ecmascript-proposals.md @@ -0,0 +1,144 @@ +# EcmaScript Proposals + + + + +Table of EcmaScript proposals and their usage in **Jail**. + +| Proposal | Status | Information | Jali | Polyfill | TypeScript | Babel Plugin | Babel Preset | [Node][NodeESNext]| Comment | +|:-------------------------------------|-------------|:-----------------:|:----:|:---------|:------------:|:------------------------------------|:---------------|:-------------|:--------------------------------------------------------| +| [modules][1] | ES2015 | [MDN01]
[MDN02]| Yes | | Yes | [transform-es2015-modules-commonjs] | [es2015-node6]
[node6]| | Babel transform not used as modules are supported by Webpack 2.
Used by AVA based unit testing | +| [function.name property][2] | ES2015 | [MDN03] | Yes | | Yes | [transform-es2015-function-name] | [es2015-node6] | 6 LTS | Transform used for browser. | +| [exponentiation operator][3] | ES2016 | [MDN04] | Yes | | Yes | [transform-exponentiation-operator] | [es2016] | 7 | | +| [Array#includes][42] | ES2016 | [MDN05] | Yes | core.js | Yes | | [es2016] | 6 LTS | | +| [Object.{values,entries}][4] | ES2017 | [MDN06]
[MDN07]| Yes | core.js | Yes | | | 7 | | +| [String#{padStart,padEnd}][5] | ES2017 | [MDN08]
[MDN09]| Yes | core.js | Yes | | | | | +| [Object.getOwnPropertyDescriptors][6]| ES2017 | [MDN10] | Yes | core.js | Yes | | | | | +| [trailing commas from function calls][9]| ES2017 | [2ality00] | Yes | | Yes | [syntax-trailing-function-commas] | [node6] | | | +| [Async Functions][8] | ES2017 | [MDN11] | Yes | | Yes | [transform-async-to-generator] | | | | +| [SIMD][7] | Stage 3 | [MDN12] | | | | | | | Not included.
Can use [polyfill](https://www.npmjs.com/package/simd). | +| [Function#toString revision][10] | Stage 3 | [2ality01] | | | | | | | ??? | +| [Lifting Template Literal Restriction][24]| Stage 3| [2ality02] | | | #[12700] | | | | | +| [global][15] | Stage 3 | [2ality03] | | | #[12902] | | | | core.js implementation still has System.global | +| [Rest/Spread Properties][13] | Stage 3 | [2ality04] | Yes | | Yes | [transform-object-rest-spread] | | | Introduced in TypeScript [2.1][ts21] | +| [Asynchronous Iteration][11] | Stage 3 | [2ality05] | | | 2.2 #[11326] | [transform-async-to-generator] | | | | +| [Shared memory and atomics][14] | Stage 3 | [MDN13] | | | | | | | Nope. | +| [import()][34] | Stage 3 | [2ality06] | | | #[12364] | | | | Webpack 2 [supports it][code-splitting-with-es2015] | +| [function.sent metaproperty][12] | Stage 2 | | Yes | | | [transform-function-sent] | | | | +| [String.prototype.{trimStart,trimEnd}][17]| Stage 2| | Yes | core.js | | | | | | +| [Public Class Fields][20] | Stage 2 | | Yes | | Yes. See comment| [transform-class-properties] | | | Bug in TypeScript #[12212] does not pass through to Babel| +| [Promise#finally][16] | Stage 2 | | | | | | | | [promise.prototype.finally][promise.prototype.finally] | +| [Class and Property Decorators][18] | Stage 2 | | Yes | | Yes | [transform-decorators-legacy] | | | [Needs write in Babel][babel-2016-12-07] | +| [Legacy RegExp features in JavaScript][32]| Stage 2| | | | | | | | | +| [RegExp Lookbehind Assertions][39] | Stage 2 | | | | | | | | | +| [RegExp Unicode Property Escapes][23]| Stage 2 | | | | | | | | | +| [Private Fields][28] | Stage 2 | | | | #[9950] | PR #[260] | | | | +| [Intl.Segmenter][36] | Stage 2 | | | | | | | | | +| [Date.parse fallback semantics][25] | Stage 1 | | | | | | | | | +| [export ns from][26] | Stage 1 | | | | | | | | | +| [export default from][27] | Stage 1 | | | | | | | | | +| [Observable][19] | Stage 1 | | Yes | core.js | | | | | | +| [String#matchAll][41] | Stage 1 | | | | | | | | | +| [WeakRefs][29] | Stage 1 | | | | | | | | | +| [Frozen Realms][30] | Stage 1 | | | | | | | | | +| [Math Extensions][22] | Stage 1 | | | | | | | | | +| [`of` and `from` on collection constructors][33]| Stage 1| | | | | | | | | +| Generator arrow functions | Stage 1 | | | | | | | | | +| [RegExp named capture groups][35] | Stage 1 | | | | | | | | | +| [`s` (`dotAll`) flag for regular expressions][37]| Stage1| | | | | | | | | +| [`Promise.try`][38] | Stage 1 | | | | | | | | | +| [64-Bit Integer Operations][40] | Stage 1 | | | | | | | | | +| [String.prototype.at][21] | Stage 0 | | Yes | core.js | | | | | | + +[1]: http://www.ecma-international.org/ecma-262/6.0/#sec-modules +[2]: http://www.ecma-international.org/ecma-262/6.0/#sec-setfunctionname +[3]: https://github.com/rwaldron/exponentiation-operator +[4]: https://github.com/tc39/proposal-object-values-entries +[5]: https://github.com/tc39/proposal-string-pad-start-end +[6]: https://github.com/ljharb/proposal-object-getownpropertydescriptors +[7]: https://docs.google.com/presentation/d/1MY9NHrHmL7ma7C8dyNXvmYNNGgVmmxXk8ZIiQtPlfH4/edit?usp=sharing +[8]: https://github.com/tc39/ecmascript-asyncawait +[9]: https://jeffmo.github.io/es-trailing-function-commas/ +[10]: https://tc39.github.io/Function-prototype-toString-revision +[11]: https://github.com/tc39/proposal-async-iteration +[12]: https://github.com/allenwb/ESideas/blob/master/Generator%20metaproperty.md +[13]: https://github.com/sebmarkbage/ecmascript-rest-spread +[14]: https://github.com/tc39/ecmascript_sharedmem +[15]: https://github.com/tc39/proposal-global +[16]: https://github.com/tc39/proposal-promise-finally +[17]: https://github.com/sebmarkbage/ecmascript-string-left-right-trim +[18]: http://tc39.github.io/proposal-decorators/ +[19]: https://github.com/tc39/proposal-observable +[20]: https://tc39.github.io/proposal-class-public-fields/ +[21]: https://github.com/mathiasbynens/String.prototype.at +[22]: https://github.com/rwaldron/proposal-math-extensions +[23]: https://github.com/tc39/proposal-regexp-unicode-property-escapes +[24]: https://github.com/tc39/proposal-template-literal-revision +[25]: https://github.com/mrrrgn/proposal-date-time-string-format +[26]: https://github.com/leebyron/ecmascript-export-ns-from +[27]: https://github.com/leebyron/ecmascript-export-default-from +[28]: https://github.com/zenparsing/es-private-fields +[29]: https://github.com/tc39/proposal-weakrefs +[30]: https://github.com/FUDCo/frozen-realms +[32]: https://github.com/tc39/proposal-regexp-legacy-features +[33]: https://github.com/leobalter/proposal-setmap-offrom +[34]: https://github.com/tc39/proposal-dynamic-import +[35]: https://github.com/tc39/proposal-regexp-named-groups +[36]: https://github.com/tc39/proposal-intl-segmenter +[37]: https://github.com/mathiasbynens/es-regexp-dotall-flag +[38]: https://github.com/ljharb/proposal-promise-try +[39]: https://github.com/tc39/proposal-regexp-lookbehind +[40]: https://gist.github.com/BrendanEich/4294d5c212a6d2254703 +[41]: https://github.com/tc39/String.prototype.matchAll +[42]: https://github.com/tc39/Array.prototype.includes/ + +[syntax-trailing-function-commas]: https://babeljs.io/docs/plugins/syntax-trailing-function-commas/ +[transform-async-to-generator]: http://babeljs.io/docs/plugins/transform-async-to-generator/ +[transform-class-properties]: https://babeljs.io/docs/plugins/transform-class-properties/ +[transform-decorators-legacy]: https://babeljs.io/docs/plugins/transform-decorators/ +[transform-es2015-modules-commonjs]: https://babeljs.io/docs/plugins/transform-es2015-modules-commonjs/ +[transform-es2015-function-name]: https://babeljs.io/docs/plugins/transform-es2015-function-name/ +[transform-exponentiation-operator]: https://babeljs.io/docs/plugins/transform-exponentiation-operator/ +[transform-function-sent]: https://www.npmjs.com/package/babel-plugin-transform-function-sent +[transform-object-rest-spread]: https://babeljs.io/docs/plugins/transform-object-rest-spread/ + +[es2015-node6]: https://www.npmjs.com/package/babel-preset-es2015-node6 +[es2016]: https://www.npmjs.com/package/babel-preset-es2016 +[node6]: https://www.npmjs.com/package/babel-preset-node6 + +[260]: https://github.com/babel/babylon/pull/260 +[9950]: https://github.com/Microsoft/TypeScript/issues/9950 +[4576]: https://github.com/babel/babel/pull/4576 +[11326]: https://github.com/Microsoft/TypeScript/issues/11326 +[12212]: https://github.com/Microsoft/TypeScript/issues/12212 +[12364]: https://github.com/Microsoft/TypeScript/issues/12364 +[12700]: https://github.com/Microsoft/TypeScript/issues/12700 +[12902]: https://github.com/Microsoft/TypeScript/issues/12902 + +[code-splitting-with-es2015]: https://webpack.js.org/guides/migrating/#code-splitting-with-es2015 + +[2ality00]: http://www.2ality.com/2015/11/trailing-comma-parameters.html +[2ality01]: http://www.2ality.com/2016/08/function-prototype-tostring.html +[2ality03]: http://www.2ality.com/2016/09/global.html +[2ality02]: http://www.2ality.com/2016/09/template-literal-revision.html +[2ality04]: http://www.2ality.com/2016/10/rest-spread-properties.html +[2ality05]: http://www.2ality.com/2016/10/asynchronous-iteration.html +[2ality06]: http://www.2ality.com/2017/01/import-operator.html +[babel-2016-12-07]: https://babeljs.io/blog/2016/12/07/the-state-of-babel +[MDN01]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import +[MDN02]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export +[MDN03]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name +[MDN04]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation_(**) +[MDN05]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes +[MDN06]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values +[MDN07]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries +[MDN08]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart +[MDN09]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd +[MDN10]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors +[MDN11]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function +[MDN12]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SIMD +[MDN13]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer +[NodeESNext]: http://node.green/ +[promise.prototype.finally]: https://www.npmjs.com/package/promise.prototype.finally +[ts21]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html + diff --git a/esdoc.json b/esdoc.json new file mode 100644 index 0000000..213857b --- /dev/null +++ b/esdoc.json @@ -0,0 +1,14 @@ +{ + "title": "Jali specification-driven serverless microservice framework", + "source": "./dist/all", + "excludes": ["/testing/"], + "destination": "./dist/docs", + "autoPrivate": false, + "manual": { + "overview": ["./docs/esdoc/overview.md"], + "example": [ + "./dist/docs/examples/packages/jali_util.md" + ] + }, + "lint": false +} \ No newline at end of file diff --git a/examples/example-context.ts b/examples/example-context.ts new file mode 100644 index 0000000..d5d749d --- /dev/null +++ b/examples/example-context.ts @@ -0,0 +1,189 @@ +import 'reflect-metadata'; + + +import * as fs from 'fs'; +import { EOL } from 'os'; +import * as path from 'path'; + +import * as mkdirp from 'mkdirp'; +import * as sanitize from 'sanitize-filename'; +import * as ts from 'typescript'; + +import Example from './example'; +import ExampleMetadata from './example-metadata' +import FilePath from './file-path'; + + +export default class ExampleContext { + public readonly console: Console; + + public readonly mdFile: string; + public readonly metadata: ExampleMetadata + + private _markerIndex = 0; + public get markerIndex() { return this._markerIndex; } + + public constructor( + public readonly filePath: FilePath, + public readonly obj: Object, + public readonly fn: Function | undefined, + public readonly fileSourceText: string, + public readonly fileSource: ts.SourceFile, + public readonly clsSource?: ts.ClassDeclaration, + public readonly fnSource?: ts.MethodDeclaration, + public readonly indent = 2, + public readonly width = 80, + console?: Console) { + const clsBaseFileName = sanitize(obj.constructor.name); + + this.mdFile = path.join(filePath.mdRootDir, clsBaseFileName + '.md'); + + mkdirp.sync(filePath.mdRootDir); + + if (!fn) { + fs.closeSync(fs.openSync(this.mdFile, 'w')); + } + + this.console = console || global.console; + + const clsMetadata = Example.getMetadata(obj.constructor); + const fnMetadata = fn ? Example.getMetadata(obj, fn.name) : undefined; + + this.metadata = { + pkg: fnMetadata && fnMetadata.pkg || clsMetadata.pkg, + module: fnMetadata && fnMetadata.module || clsMetadata.module, + type: fnMetadata && fnMetadata.type || clsMetadata.type, + member: fnMetadata && fnMetadata.member || clsMetadata.member, + } + } + + public static indentLines( + depth: number, message: string, indent = 2, width = 80, marker?: string): string { + + const markerLength = (marker) ? marker.length : 0; + const indentationLength = indent * depth; + const columnWidth = width - indentationLength - markerLength; + const indentation = ' '.repeat(indentationLength); + const lines = message.split(/\r?\n/); + + let indentedLines: string[] = []; + + for (const line of lines) { + let marked = !marker ? true : false; + let lineFragment = line; + + while (lineFragment.length > columnWidth) + { + let lastSpaceIndex = lineFragment.lastIndexOf(' ', columnWidth - 1); + + if (lastSpaceIndex === -1) { + let indentedLine = indentation + lineFragment.substring(0, columnWidth); + + if (!marked) { + indentedLine += ' '.repeat(columnWidth - indentedLine.length - markerLength) + marker; + marked = true; + } + + indentedLines.push(indentedLine); + + lineFragment = lineFragment.substr(columnWidth); + } + else { + let indentedLine = indentation + lineFragment.substring(0, lastSpaceIndex); + if (!marked){ + indentedLine += ' '.repeat(columnWidth - indentedLine.length - markerLength) + marker; + marked = true; + } + + indentedLines.push(indentedLine); + + if (lastSpaceIndex === lineFragment.length) { + lineFragment = ''; + } + else { + lineFragment = lineFragment.substr(lastSpaceIndex + 1); + } + } + } + + let indentedLine = indentation + lineFragment; + + if (!marked) { + indentedLine += ' '.repeat(columnWidth - line.length - markerLength) + marker; + marked = true; + } + + indentedLines.push(indentedLine); + } + + return indentedLines.join(EOL); + } + + public log(message: string = '') { + this.console.log(message); + fs.appendFileSync(this.mdFile, message + EOL); + } + + public logHeader() { + const metadata = this.metadata; + + const name = this.fn ? this.fn.name : this.obj.constructor.name; + const description = metadata.member + ? `Examples for package ${metadata.pkg ? `\`${metadata.pkg}\` ` : ''}submodule ` + + `\`${metadata.module}\` type ${metadata.type ? `\`${metadata.type}\` `: ''}member ` + + `\`${metadata.member}\`` + : metadata.type + ? `Examples for package ${metadata.pkg ? `\`${metadata.pkg}\` ` : ''}submodule ` + + `\`${metadata.module}\` type \`${metadata.type}\`` + : metadata.module + ? `Examples for package ${metadata.pkg ? `\`${metadata.pkg}\` ` : ''}submodule ` + + `\`${metadata.module}\`` + : metadata.pkg + ? `Examples for package \`${metadata.pkg}\`` + : `Examples in \`${name}\``; + + const hdr = (this.fn) ? '## ' : '# '; + + const message = `${hdr}${name}${EOL}${description}${EOL}`; + + this.log(message) + } + + public logIndented(depth: number, message: string, marker?: string | boolean): void { + + const logMessage = Class.indentLines(depth, message, this.indent, this.width, this.getMarker(marker)); + + this.log(logMessage); + } + + public logException(depth: number, fn: () => void, marker?: string | boolean): void { + try { + fn(); + } + catch (err) { + this.logIndented(depth, err.toString(), this.getMarker(marker)); + } + } + + private getMarker(marker?: string | boolean): string | undefined { + if (!marker) { + return undefined; + } + + if (typeof marker === 'string') { + return marker; + } + + const markerNumber = this._markerIndex + 1; + this._markerIndex = this._markerIndex + 1; + + if (markerNumber <= 20) { + return String.fromCharCode(0x2460 + markerNumber); + } + + const markerNumerals = markerNumber.toString(); + + return [...markerNumerals].map(char => String.fromCharCode(0x2460 + parseInt(char))).join(); + } +} +const Class = ExampleContext; // tslint:disable-line:variable-name diff --git a/examples/example-metadata.ts b/examples/example-metadata.ts new file mode 100644 index 0000000..594e3fc --- /dev/null +++ b/examples/example-metadata.ts @@ -0,0 +1,8 @@ +interface ExampleMetadata { + pkg: string, + module?: string, + type?: string, + member?: string, +} + +export default ExampleMetadata; \ No newline at end of file diff --git a/examples/example-runner-options.ts b/examples/example-runner-options.ts new file mode 100644 index 0000000..b0c783c --- /dev/null +++ b/examples/example-runner-options.ts @@ -0,0 +1,11 @@ +interface ExampleRunnerOptions { + include?: string, + rootDir: string, + mdDir: string, + tsDir?: string, + indent?: number, + width?: number, + console?: Console, +} + +export default ExampleRunnerOptions; \ No newline at end of file diff --git a/examples/example-runner.ts b/examples/example-runner.ts new file mode 100644 index 0000000..d50d528 --- /dev/null +++ b/examples/example-runner.ts @@ -0,0 +1,320 @@ +// cSpell:ignore readdir ᴇxα΄€α΄α΄©ΚŸα΄‡ α΄α΄œα΄›α΄©α΄œα΄› + +import 'reflect-metadata'; + +import * as path from 'path'; +import * as fs from 'fs'; +import { EOL } from 'os'; + +import * as appRoot from 'app-root-path'; +import * as glob_fs from 'glob-fs'; +import * as ts from 'typescript'; + +import Example from './example'; +import ExampleRunnerOptions from './example-runner-options'; +import ExampleContext from './example-context'; +import FilePath from './file-path'; + +export default class ExampleRunner { + public readonly options: ExampleRunnerOptions; + + public constructor(options: ExampleRunnerOptions) { + this.options = { + console: options.console || global.console, + include: options.include || path.resolve(this.options.rootDir, './**/*.example.js'), + indent: options.indent || 2, + mdDir: options.mdDir, + rootDir: options.rootDir, + tsDir: options.tsDir || options.rootDir, + width: options.width || 80, + } + } + + public run(): boolean { + const glob = glob_fs({gitignore: true/*, realPath: true*/}); + + const errors: string[] = []; + + const mdDir = appRoot.resolve(this.options.mdDir) as string; + const rootDir = Class.validatePathAndGetAbsolute(this.options.rootDir, 'rootDir', errors); + const tsDir = Class.validatePathAndGetAbsolute(this.options.tsDir as string, 'tsDir', errors); + + const files = glob.readdirSync(this.options.include) as string[]; + + const filePaths: (FilePath | undefined)[] = files.map(f => Class.mapFilePath( + f, rootDir, tsDir, mdDir, errors)); + + if (errors.length > 0) { + console.log(errors.join(EOL)); + return false; + } + + let hasErrors = false; + for (const filePath of filePaths) { + if (!filePath) { + continue; + } + const localHasErrors = this.processFile(filePath); + + hasErrors = hasErrors || localHasErrors; + } + + return hasErrors; + } + + + private static getFileSource(tsPath: string): ts.SourceFile { + const source = fs.readFileSync(tsPath, { + encoding: 'utf8', + }); + + const { base } = path.parse(tsPath); + + return ts.createSourceFile(base, source, ts.ScriptTarget.Latest, true); + } + + private static *getFnContexts(clsCtx: ExampleContext): Iterable { + // // From http://stackoverflow.com/a/35033472/2240669 + // // and http://stackoverflow.com/a/31055009/2240669 + // const methodNames = Object.getOwnPropertyNames(cls.prototype) + const methodNames = Object.getOwnPropertyNames(clsCtx.obj.constructor.prototype) + .filter((name) => name !== 'constructor' && typeof (clsCtx.obj as any)[name] === 'function'); + + for (const methodName of methodNames) { + const methodMetadata = Example.getMetadata(clsCtx.obj, methodName); + + if (methodMetadata) { + const fn = (clsCtx.obj as any)[methodName]; + + let errors: string[] = []; + + const methodSource = Class.getMethodSource( + (clsCtx.clsSource as ts.ClassDeclaration), methodName, errors); + + if (!methodSource) { + clsCtx.log(errors.join(EOL)); + return false; + } + + if (!methodSource.body) { + continue; + } + + const fnCtx = new ExampleContext( + clsCtx.filePath, + clsCtx.obj, + fn, + clsCtx.fileSourceText, + clsCtx.fileSource, + clsCtx.clsSource, + methodSource, + clsCtx.indent, + clsCtx.width, + clsCtx.console); + + yield fnCtx; + } + } + } + + private static mapFilePath( + file: string, rootDir: string, tsDir: string, mdDir: string, errors: string[]): FilePath | undefined { + const jsPath = Class.validatePathAndGetAbsolute(file, 'include', errors, rootDir); + const fileFragment = jsPath.substr(rootDir.length + 1); + const fragments = path.parse(fileFragment); + + const tsPath = path.join(tsDir, fragments.dir, fragments.name + ".ts"); + Class.validatePath(tsPath, 'TypeScript file', errors); + + if (errors.length > 0) { + return undefined; + } + + const mdFilePrefix = path.resolve(mdDir, fragments.dir); + + const filePath: FilePath = { + jsPath: jsPath, + tsPath: tsPath, + mdRootDir: mdFilePrefix, + }; + + return filePath; + } + + private static validatePath(path: string, name: string, errors: string[]): void { + try { + fs.accessSync(path, fs.constants.R_OK); + } + catch (err) { + errors.push(`Cannot access '${name}' path '${path}'`); + } + } + + private static validatePathAndGetAbsolute( + path: string, name: string, errors: string[], root?: string): string { + const full = appRoot.resolve(path) as string; + + this.validatePath(full, name, errors); + + if (root && + (full.length < root.length || + full.toUpperCase().substr(0, root.length) != root.toUpperCase())) { + errors.push(`Path for '${name}' of ${full} does not have specified path root '${root}'`); + } + + return full; + } + + private static getClassName(cls: ts.ClassDeclaration): string | undefined { + const classIdentifier = cls.name as ts.Identifier; + + if (classIdentifier === undefined) { + return undefined; + } + + if (classIdentifier.kind !== ts.SyntaxKind.Identifier) { + return undefined; + //const kind = ts.SyntaxKind[classIdentifier.kind]; + //throw new Error(`Class name as pos '${classIdentifier.pos}' must be 'Identifier'. Yours is '${kind}'`); + } + + return classIdentifier.text; + } + + private static getClassSource( + tsPath: string, sourceFile: ts.SourceFile, clsName: string, errors: string[]) + : ts.ClassDeclaration | undefined { + const fileChildren = sourceFile.getChildren(); + + if (fileChildren.length === 0) { + errors.push(`Example file '${tsPath}' has no children.`); + return undefined; + } + + + const syntaxList = fileChildren[0]; + if (syntaxList.kind !== ts.SyntaxKind.SyntaxList) { + errors.push(`Example file '${tsPath}' child is not a 'SyntaxList'.`); + return undefined; + } + + + const clsDeclaration = syntaxList.getChildren().find(n => + n.kind === ts.SyntaxKind.ClassDeclaration && + Class.getClassName((n as ts.ClassDeclaration)) === clsName) as ts.ClassDeclaration; + + return clsDeclaration; + } + + private static getMethodName(method: ts.MethodDeclaration): string | undefined { + return (method.name.kind === ts.SyntaxKind.Identifier) + ? (method.name as ts.Identifier).text + : undefined; + } + + private static getMethodSource(cls: ts.ClassDeclaration, name: string, errors: string[]) + : ts.MethodDeclaration | undefined { + const memberLists = cls.getChildren().filter(n => + n.kind === ts.SyntaxKind.SyntaxList && + n.getChildren().some(n => n.kind === ts.SyntaxKind.MethodDeclaration)); + + if (memberLists.length === 0) { + errors.push(`Example class '${Class.getClassName(cls)}' does not have any methods`); + return undefined; + } + + if (memberLists.length > 1) { + errors.push(`INTERNAL ERROR: Example class '${Class.getClassName(cls)}' cannot be parsed: 'Description: Multiple SyntaxLists'`); + return undefined; + } + + const methods = memberLists[0].getChildren().filter( + n => n.kind === ts.SyntaxKind.MethodDeclaration) as ts.MethodDeclaration[]; + + const method = methods.find(m => Class.getMethodName(m) === name); + + if (!method) { + errors.push(`Example class '${Class.getClassName(cls)}' does not have a method '${name}'`); + return undefined; + } + + return method; + } + + private processFile(filePath: FilePath): boolean { + // From https://github.com/esnext/es6-module-transpiler/issues/85 + const cls = (require(filePath.jsPath)).default; + + const classMetadata = Example.getMetadata(cls); + + if (classMetadata) { + const obj = new cls(); + + const fileSourceText = fs.readFileSync(filePath.tsPath, { + encoding: 'utf8', + }); + + const fileSource = Class.getFileSource(filePath.tsPath); + + const errors: string[] = []; + const clsSource = Class.getClassSource(filePath.tsPath, fileSource, obj.constructor.name, errors) + + const clsCtx = new ExampleContext( + filePath, + obj, + undefined, + fileSourceText, + fileSource, + clsSource, + undefined, + this.options.indent, + this.options.width, + this.options.console); + + clsCtx.logHeader(); + + if (!clsSource) { + clsCtx.log(errors.join(EOL)); + return false; + } + + let firstFunction = true; + let noErrors = true; + + for (const fnCtx of Class.getFnContexts(clsCtx)) { + if (firstFunction) { + firstFunction = false; + } + else { + fnCtx.log(); + } + noErrors = noErrors && this.processFunction(fnCtx); + } + + return noErrors; + } + + return true; + } + + private processFunction(fnCtx: ExampleContext): boolean { + fnCtx.logHeader(); + + fnCtx.log('**ᴇxα΄€α΄α΄©ΚŸα΄‡**') + fnCtx.log('```typescript'); + + const body = (fnCtx.fnSource as ts.MethodDeclaration).body; + + fnCtx.log(fnCtx.fileSourceText.substring(body!.pos, body!.end) + .replace(/\r?\n/g, EOL)); + + fnCtx.log('```'); + fnCtx.log('
**α΄α΄œα΄›α΄©α΄œα΄›**') + fnCtx.log('```'); + (fnCtx.fn as Function)(fnCtx); + fnCtx.log('```'); + + return true; + } +} +const Class = ExampleRunner; // tslint:disable-line:variable-name diff --git a/examples/example.ts b/examples/example.ts new file mode 100644 index 0000000..218a919 --- /dev/null +++ b/examples/example.ts @@ -0,0 +1,29 @@ +import 'reflect-metadata'; + + +import ExampleMetadata from './example-metadata'; + +// https://www.typescriptlang.org/docs/handbook/declaration-merging.html +function Example( + pkg: string, module?: string, type?: string, member?: string) { + + const metadata: ExampleMetadata = { + pkg: pkg, + module: module, + type: type, + member: member, + } + + return Reflect.metadata(Example.METADATA_KEY, metadata); +} + +module Example { + export const METADATA_KEY = Symbol('jalidev-example'); + export const getMetadata = + function(target: Object, methodName?: string): ExampleMetadata { + return Reflect.getMetadata(METADATA_KEY, target, methodName as string) as ExampleMetadata; + } +} + + +export default Example; diff --git a/examples/file-path.ts b/examples/file-path.ts new file mode 100644 index 0000000..b785a60 --- /dev/null +++ b/examples/file-path.ts @@ -0,0 +1,7 @@ +interface FilePath { + jsPath: string, + tsPath: string, + mdRootDir: string, +} + +export default FilePath; \ No newline at end of file diff --git a/examples/helpers.ts b/examples/helpers.ts new file mode 100644 index 0000000..e69de29 diff --git a/examples/index.ts b/examples/index.ts new file mode 100644 index 0000000..e809177 --- /dev/null +++ b/examples/index.ts @@ -0,0 +1,15 @@ +// import * as path from 'path'; + +import ExampleRunner from './example-runner'; + + +const runner = new ExampleRunner({ + console: global.console, + include: './dist/examples/**.example.js', + rootDir: './dist/examples/examples', + mdDir: './dist/docs/examples', + tsDir: './examples', + width: 100, +}) + +runner.run(); diff --git a/examples/packages/util.example.ts b/examples/packages/util.example.ts new file mode 100644 index 0000000..47e946d --- /dev/null +++ b/examples/packages/util.example.ts @@ -0,0 +1,432 @@ +// cSpell:ignore asiterable tomap +// import * as path from 'path'; + +import * as Util from '@jali/util'; +import * as Errors from '@jali/util/errors'; +import { verifyArgument, verifyTruthy } from '@jali/util/errors'; +import * as Iterables from '@jali/util/iterables'; +import * as TypeGuards from '@jali/util/type-guards'; + +import Example from '../example'; +import ExampleContext from '../example-context' + +@Example('@jali/util') +export default class jali_util { + + @Example('@jali/util', '@jali/util/errors') + public jali_util_errors(writer: ExampleContext): void { + // Demonstrates verifying function arguments for low-level libraries. In service operations, + // use Jali Notification Messages instead. + function functionWithParameters( + notNullNumber: number, notWhitespaceString: string, truthyBoolean: boolean): void { + + // Verifying JavaScript type or common scenarios. Often redundant in TypeScript but needed if + // called otherwise. See examples β‘ , β‘‘, & β‘’. + Util.Errors.verifyNotNull('nonNullNumber', notNullNumber); + Errors.verifyNotWhitespace('notWhitespaceString', notWhitespaceString); + verifyTruthy('truthyBoolean', truthyBoolean); + + // Verifying invariants. In this case, the argument cannot contain a whitespace character + // anywhere. See example β‘£. + verifyArgument('notWhitespaceString', notWhitespaceString, arg => !arg.match(/\w/u)); + + // Verifying with a custom message. In this case, the permitted range is specified. See + // example β‘€. + verifyArgument( + 'notNullNumber', + notNullNumber, + arg => arg >= 10 && arg < 20, + arg => `Argument must be between 10 and 19. Yours is '${arg}'`) + } + + writer.logIndented(2, `Example for function 'verifyNotNull'`, 'β‘ '); + writer.logException(3, () => functionWithParameters(null as any as number, 'value', true)); + + writer.log(); + + writer.logIndented(2, `Example for function 'verifyNotWhitespace'`, 'β‘‘'); + writer.logException(3, () => functionWithParameters(1, ' \t\v', true)); + + writer.log(); + + writer.logIndented(2, `Example for function 'verifyTruthy'`, 'β‘’'); + writer.logException(3, () => functionWithParameters(1, 'value', NaN as any as boolean)); + + writer.log(); + + // U+1680 OGHAM SPACE MARK + writer.logIndented(2, `Example for function 'verifyArgument'`, 'β‘£'); + writer.logException(3, () => functionWithParameters(1, 'HasA\u{1680}Space', true)); + + writer.log(); + + writer.logIndented( + 2, `Example for function 'verifyArgument' with specified message function`, 'β‘€'); + + writer.logException(3, () => functionWithParameters(20, 'value', true)); + + writer.log(); + } + + /** + * @/jali/util/iterables.asArray + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'asArray') + public jali_util_iterators_asarray(writer: ExampleContext): void { + writer.logIndented(2, `number to number[]`, 'β‘ '); + + const numberOrNumbers: number | Iterable = 2; + const array = Iterables.asArray(numberOrNumbers); + const isArrayOfNumber = TypeGuards.makeIsIterable(e => typeof e === 'number', true); + + const output = + `Is '${array}' a 'number[]' (${isArrayOfNumber(array)}) with length '1' ` + + `(${array.length === 1}) and first value of '2'? '${Iterables.find(array) === 2}'`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `string to string[]`, 'β‘‘'); + + const stringOrStrings: string | Iterable = 'DodgerBlue'; + const array2 = Iterables.asArray(stringOrStrings, String); + const array3 = Iterables.asArray(stringOrStrings); + const isArrayOfString = TypeGuards.makeIsIterable(e => typeof e === 'string', true); + + const output2 = + `Is '${array2}' a 'string[]' (${isArrayOfString(array2)}) with length '1' ` + + `(${array2.length === 1}) and first value of 'DodgerBlue'? ` + + `'${Iterables.find(array2) === 'DodgerBlue'}'`; + + writer.logIndented(3, output2); + + const output3 = + `Is '${array3}' a 'string[]' (${isArrayOfString(array3)}) with length '1' ` + + `(${array3.length === 1}) and first value of 'DodgerBlue'? ` + + `'${Iterables.find(array3) === 'DodgerBlue'}'`; + + writer.logIndented(3, output3); + } + + /** + * @/jali/util/iterables.asIterable + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'asIterable') + public jali_util_iterators_asiterable(writer: ExampleContext): void { + writer.logIndented(2, `undefined to Iterable`, 'β‘ '); + + const numberOrNumbers: number | Iterable | undefined = undefined; + const iterable = Iterables.asIterable(numberOrNumbers); + const isIterableOfNumber = TypeGuards.makeIsIterable(e => typeof e === 'number', true); + + const output = + `Is '${iterable}' an 'Iterable' (${isIterableOfNumber(iterable)}) with length '0': ` + + `'${[...iterable].length === 0}'`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `string to Iterable`, 'β‘‘'); + + const stringOrStrings: string | Iterable = 'DodgerBlue'; + const iterable2 = Iterables.asIterable(stringOrStrings, String); + const iterable3 = Iterables.asIterable(stringOrStrings); + const isIterableOfString = TypeGuards.makeIsIterable(e => typeof e === 'string', true); + + const output2 = + `Is '${iterable2}' an 'Iterable' (${isIterableOfString(iterable2)}) with length ` + + `'1' (${[...iterable2].length === 1}) and first value of 'DodgerBlue'? ` + + `'${Iterables.find(iterable2) === 'DodgerBlue'}'`; + + writer.logIndented(3, output2); + + const output3 = + `Is '${iterable3}' a 'Iterable' (${isIterableOfString(iterable3)}) with length ` + + `'1' (${[...iterable3].length === 1}) and first value of 'DodgerBlue'? ` + + `'${Iterables.find(iterable3) === 'DodgerBlue'}'`; + + writer.logIndented(3, output3); + } + + /** + * @/jali/util/iterables.concat + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'concat') + public jali_util_iterators_concat(writer: ExampleContext): void { + writer.logIndented(2, `Concatenate three sequences.`, 'β‘ '); + const weekendDays = ['Sunday', 'Saturday']; + const workWeekDays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; + + const daysOfWeek = Iterables.concat([weekendDays[0]], workWeekDays, [weekendDays[1]]); + writer.logIndented(3, `Days of Week: '${Iterables.asArray(daysOfWeek)}`); + } + + /** + * @/jali/util/iterables.every + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'every') + public jali_util_iterators_every(writer: ExampleContext): void { + writer.logIndented(2, `Test for only even numbers`, 'β‘ '); + + const numbers = [2, 6, 10, 22, 999]; + const result = Iterables.every(numbers, e => e % 2 === 0); + + const output = + `Sequence '${numbers}' ${result ? 'has' : 'does not have'} only even values.`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `Test that all elements are multiple of position`, 'β‘‘'); + const numbers2 = [1, 6, 6, 24]; + const multiples = Iterables.every(numbers2, (e, i) => e / (i + 1) % 1 === 0); + + const output2 = + `Elements of sequence '${numbers2}' ${multiples ? 'are' : 'are not'} multiples of their position.`; + + writer.logIndented(3, output2); + } + + /** + * @/jali/util/iterables.filter + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'filter') + public jali_util_iterators_filter(writer: ExampleContext): void { + writer.logIndented(2, `Select even numbers`, 'β‘ '); + + const numbers = [2, 6, 10, 22, 999]; + const evens = Iterables.filter(numbers, e => e % 2 === 0); + + const output = + `Sequence '${numbers}' has these even values: '${Iterables.asArray(evens)}'.`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `Select every other element`, 'β‘‘'); + const numbers2 = [1, 6, 6, 24]; + const evenIndexed = Iterables.filter(numbers2, (_, i) => i % 2 === 0); + + const output2 = + `Even indexed elements of sequence '${numbers2}' are '${Iterables.asArray(evenIndexed)}'.`; + + writer.logIndented(3, output2); + } + + /** + * @/jali/util/iterables.find + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'find') + public jali_util_iterators_find(writer: ExampleContext): void { + writer.logIndented(2, `Find first element divisible by 5`, 'β‘ '); + + const numbers = [2, 6, 10, 22, 999]; + const divisible = Iterables.find(numbers, e => e % 5 === 0); + + const output = + `The first element of '${numbers}' divisible by five is: '${divisible}'.`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `Select fourth element`, 'β‘‘'); + const numbers2 = [1, 6, 6, 24]; + const fourth = Iterables.find(numbers2, (_, i) => i === 3); + + const output2 = + `The fourth element of sequence '${numbers2}' is '${fourth}'.`; + + writer.logIndented(3, output2); + + writer.log(); + + writer.logIndented(2, `Get the first element`, 'β‘’'); + const first = Iterables.find(numbers); + + const output3 = + `The first element in sequence '${numbers}' is '${first}'.`; + + writer.logIndented(3, output3); + } + + /** + * @/jali/util/iterables.includes + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'includes') + public jali_util_iterators_includes(writer: ExampleContext): void { + writer.logIndented(2, `Find an element`, 'β‘ '); + + const numbers = [2, 6, 10, 22, 999]; + const didFind = Iterables.includes(numbers, 999); + const output = `Was '999' found in sequence '${numbers}'? '${didFind}'.`; + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `Don't find an element starting at an index`, 'β‘‘'); + const numbers2 = [1, 6, 6, 24]; + const didNotFind = Iterables.includes(numbers2, 1, 2); + const output2 = `Was '1' found in sequence '${numbers2}' starting at index '2'? '${didNotFind}'.`; + + writer.logIndented(3, output2); + } + + /** + * @/jali/util/iterables.map + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'map') + public jali_util_iterators_map(writer: ExampleContext): void { + writer.logIndented(2, `Transform to objects`, 'β‘ '); + + const numbers = [2, 6, 10, 22, 999]; + const objects = Iterables.map(numbers, e => { return {id: e} }); + const objectsDisplay = [...objects].map(o => JSON.stringify(o)).join(); + + const output = + `Sequence '${objectsDisplay}' has these Ids: '${numbers}'.`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `Multiply element by position`, 'β‘‘'); + const numbers2 = [1, 6, 6, 24]; + const scaled = Iterables.map(numbers2, (e, i) => e * (i + 1)); + const output2 = `Sequence '${numbers2}' elements scaled by position: '${[...scaled]}'.`; + + writer.logIndented(3, output2); + } + + /** + * @/jali/util/iterables.reduce + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'reduce') + public jali_util_iterators_reduce(writer: ExampleContext): void { + writer.logIndented(2, `Compute average`, 'β‘ '); + + let average = { count: 0, value: 0, }; + + const numbers = [2, 6, 10, 22, 999]; + const result = Iterables.reduce(numbers, (p, e) => { + return { + count: p.count + 1, + value: (p.value * p.count + e) / (p.count + 1) + } + }, average); + + const output = + `The average of sequence '${numbers}' is: '${result.value}'.`; + + writer.logIndented(3, output); + } + + /** + * @/jali/util/iterables.slice + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'slice') + public jali_util_iterators_slice(writer: ExampleContext): void { + writer.logIndented(2, `Get paged data from a store`, 'β‘ '); + + + const computeValue = (i: number) => Array.from( + 'abcde', (c, ci) => { + const offset = i % 26 - (i % 26 + ci >= 26 ? 26 : 0); + return String.fromCharCode(c.charCodeAt(0) + offset); + }).join(''); + + + const store: {id: number, value: string}[] = []; + for (let i = 0; i < 1000; i++) { + store.push({ + id: i, + value: computeValue(i), + }); + } + + const getPage = (pageNumber: number, pageSize: number) => + Iterables.slice(store, pageNumber * pageSize, pageNumber * pageSize + pageSize); + + const writePage = (pageNumber: number, page: Iterable<{id: number, value: string}>) => { + const output = + `Data for page '${pageNumber}' is '${JSON.stringify(Array.from(page))}'.`; + + writer.logIndented(3, output); + } + + const pageSize = 10; + + let pageNumber = 0; + + let page = getPage(pageNumber, pageSize); + writePage(pageNumber, page); + + writer.log(); + + pageNumber = 43; + page = getPage(pageNumber, pageSize); + writePage(pageNumber, page); + } + + /** + * @/jali/util/iterables.some + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'some') + public jali_util_iterators_some(writer: ExampleContext): void { + writer.logIndented(2, `Determine if any element is divisible by 5`, 'β‘ '); + + const numbers = [2, 6, 10, 22, 999]; + const areDivisible = Iterables.some(numbers, e => e % 5 === 0); + + const output = + `Are any element of '${numbers}' divisible by five? '${areDivisible}'.`; + + writer.logIndented(3, output); + + writer.log(); + + writer.logIndented(2, `Determine if there is a fourth element`, 'β‘‘'); + const numbers2 = [1, 6, 6, 24]; + const hasFourth = Iterables.some(numbers2, (_, i) => i === 3); + + const output2 = + `Is there a fourth element in sequence '${numbers2}'? '${hasFourth}'.`; + + writer.logIndented(3, output2); + + writer.log(); + + writer.logIndented(2, `Determine if the sequence is empty`, 'β‘’'); + const hasAny = Iterables.some(numbers2); + + const output3 = + `Is sequence '${numbers2}' empty? '${!hasAny}'.`; + + writer.logIndented(3, output3); + } + + /** + * @/jali/util/iterables.toMap + */ + @Example('@jali/util', '@jali/util/iterators', 'Iterables', 'toMap') + public jali_util_iterators_tomap(writer: ExampleContext): void { + writer.logIndented(2, `Entities mapped by id`, 'β‘ '); + + const queryResult = [ + {id: 4, firstName: 'Sam', lastName: 'Smith',}, + {id: 10, firstName: 'Janet', lastName: 'Jones',}, + {id: 7, firstName: 'Karina', lastName: 'Kelly',}, + ]; + + const byId = Iterables.toMap(queryResult, e => e.id); + + for (const id of Array.from(byId.keys()).sort((a, b) => a < b ? -1 : a === b ? 0 : 1)) { + writer.logIndented(3, JSON.stringify(byId.get(id))); + } + } +} + diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 0000000..df9888a --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": true, + "lib": ["es2017"], + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "../dist/examples/", + "paths": { + "@jali/*": ["../packages/@jali/*"] + }, + //"rootDir": ".", + "rootDirs": ["."], + "strictNullChecks": true, + "stripInternal": true, + "target": "es2015", + "typeRoots": ["../../node_modules/@types/node"], + "types": ["node"] + }, + "files": [ + "../typings/index.d.ts", + "./index.ts", + "./packages/util.example.ts" + ] + , + "include": [ + "./**.ts" + ] +} + + diff --git a/package.json b/package.json index dea12ae..8f01ec8 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,67 @@ { + "//": [ + "babel:", + "for ts+babel sourcemaps see http://stackoverflow.com/a/37799745/2240669", + "ava:", + "See https://github.com/avajs/ava/blob/master/docs/recipes/babelrc.md#transpiling-tests-and-sources-the-same-way.", + "See https://github.com/avajs/ava/blob/master/docs/recipes/code-coverage.md" + ], "name": "jali-srcs", "version": "0.0.0", "description": "Cross-platform service specification and execution context. Jali is the JavaScript implementation.", "scripts": { + "build": "npm run build:dev", + "build:packages": "tsc -p packages", + "postbuild:packages": "npm run build:examples", + "build:pkg": "echo build:pkg complete", + "prebuild:pkg": "npm run build:pkg:note", + "build:pkg:core": "tsc -p packages/@jali/core/tsconfig-build.json && cpy packages/@jali/core/package.json dist/packages-dist/@jali/core", + "prebuild:pkg:core": "npm run build:pkg:util", + "build:pkg:note": "tsc -p packages/@jali/note/tsconfig-build.json && cpy packages/@jali/note/package.json dist/packages-dist/@jali/note", + "prebuild:pkg:note": "npm run build:pkg:core", + "build:pkg:util": "tsc -p packages/@jali/util/tsconfig-build.json && webpack --config packages/@jali/util/webpackfile.js", + "build:examples": "tsc -p examples", + "build:dev": "npm run build:packages", + "prebuild:dev": "npm run clean", + "postbuild:dev": "npm run build:examples && npm run lint", + "build:docs": "npm run docs:es", + "build:test": "npm run build:packages -- --inlineSourceMap --inlineSources && npm run lint && npm run examples:run && npm run build:docs", + "prebuild:test": "npm run clean", + "build:prod": "npm run build:pkg", + "prebuild:prod": "npm run clean", + "clean": "npm run clean:dist && npm run clean:coverage", + "clean:dist": "rimraf ./dist", + "clean:coverage": "rimraf ./coverage", + "clean:all": "npm cache clean && rimraf node_modules coverage", + "preclean:all": "npm run clean", + "cover": "BABEL_ENV=test nyc ava --concurrency 5", + "precover": "npm run build:test", + "postcover": "nyc report", + "cover:html": "xdg-open ./coverage/index.html", + "precover:html": "npm run cover", + "docs": "npm run build:docs", + "predocs": "npm run build:packages && npm run examples:run", + "docs:dg": "echo ⚠ WARNING: Not operational script ⚠ && dgeni ./config/dgeni/index.js", + "docs:es": "esdoc -c esdoc.json", + "docs:td": "echo ⚠ WARNING: Not operational script ⚠ && typedoc --options typedoc.json", + "docs:html": "xdg-open ./dist/docs/index.html", + "predocs:html": "npm run docs", + "e2e": "protractor", + "pree2e": "webdriver-manager update", + "examples": "npm run examples:run", + "examples:run": "NODE_PATH=$NODE_PATH:./dist/examples/packages node ./dist/examples/examples/index.js", + "examples:run:debug": "NODE_PATH=$NODE_PATH:./dist/examples/packages node --debug-brk ./dist/examples/examples/index.js", + "preexamples": "npm run build:packages", + "install:clean": "npm install", + "preinstall:clean": "npm run clean:all", + "lint": "tslint \"packages/**/*.ts\" --format verbose", "start": "ng server", - "postinstall": "typings install", - "lint": "tslint \"src/**/*.ts\"", - "test": "ng test", - "pree2e": "webdriver-manager update", - "e2e": "protractor" + "start:clean": "npm start", + "prestart:clean": "npm run clean", + "old-test": "BABEL_ENV=test ava --tap --concurrency 5", + "test": "BABEL_ENV=test ava --concurrency 5", + "test:run:debug": "BABEL_ENV=test node --debug-brk ./node_modules/.bin/ava --tap --serial", + "pretest": "npm run build:test" }, "repository": { "type": "git", @@ -21,25 +74,133 @@ "author": "Latticework", "license": "MIT", "engines": { - "node": ">= 6.0.0" + "node": ">= 6.9.0" }, "bugs": { "url": "https://github.com/latticework/jali/issues" }, "homepage": "http://jali-ms.io/", "dependencies": { - "core-js": "^2.4.0", - "systemjs": "^0.19.30", - "zone.js": "^0.6.12" + "babel-runtime": "^6.20.0", + "core-js": "^2.4.1", + "disposables": "^1.0.1", + "espower": "^2.0.2", + "reflect-metadata": "^0.1.9", + "zone.js": "^0.7.5" }, "devDependencies": { - "blue-tape": "^0.2.0", + "@types/mkdirp": "^0.3.29", + "@types/node": "^6.0.60", + "@types/sanitize-filename": "^1.1.28", + "@types/tmp": "^0.0.32", + "app-root-path": "^2.0.1", + "awesome-typescript-loader": "^3.0.0-beta.18", + "ava": "^0.17.0", + "babel-loader": "^6.2.10", + "babel-plugin-espower": "^2.3.1", + "babel-plugin-syntax-trailing-function-commas": "^6.20.0", + "babel-plugin-transform-async-to-generator": "^6.16.0", + "babel-plugin-transform-class-properties": "^6.19.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-es2015-function-name": "^6.9.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", + "babel-plugin-transform-exponentiation-operator": "^6.8.0", + "babel-plugin-transform-function-sent": "^1.0.1", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-preset-node6": "^11.0.0", + "babel-register": "^6.18.0", + "codelyzer": "^2.0.0-beta.4", + "copy-webpack-plugin": "^4.0.1", + "cpy-cli": "^1.0.1", + "dgeni": "^0.4.2", + "dgeni-packages": "^0.16.4", + "esdoc": "^0.4.8", + "eslint": "^3.13.1", + "marked": "^0.3.6", "mkdirp": "^0.5.1", - "ts-node": "^0.9.0", - "tslint": "^3.11.0", - "typescript": "^1.8.10", - "typings": "^1.0.5", - "webpack": "^2.1.0-beta.13", - "webpack-merge": "^0.14.0" + "glob-fs": "^0.1.6", + "nyc": "^10.0.0", + "rimraf": "^2.5.4", + "sanitize-filename": "^1.6.1", + "tmp": "^0.0.31", + "ts-node": "^2.0.0", + "tslint": "^4.3.1", + "typedoc": "^0.5.5", + "typescript": "^2.1.5", + "webpack": "^2.2.0-rc.4", + "webpack-merge": "^2.4.0" + }, + "babel": { + "env": { + "development": {}, + "test": { + "plugins": [ + "transform-es2015-modules-commonjs" + ], + "sourceMaps": "inline" + }, + "production": {} + }, + "presets": [], + "plugins": [ + "syntax-trailing-function-commas", + "transform-async-to-generator", + "transform-class-properties", + "transform-decorators-legacy", + "transform-es2015-function-name", + "transform-function-sent", + "transform-exponentiation-operator", + [ + "transform-runtime", + { + "polyfill": false, + "regenerator": false + } + ], + "espower" + ], + "ignore": [ + "*.test.js" + ], + "sourceMaps": "inline" + }, + "ava": { + "files": [ + "dist/all/**/*.test.js" + ], + "concurrency": 5, + "babel": { + "plugins": [ + "syntax-trailing-function-commas", + "transform-async-to-generator", + "transform-class-properties", + "transform-decorators-legacy", + "transform-es2015-modules-commonjs", + "transform-es2015-function-name", + "transform-exponentiation-operator", + "transform-function-sent", + [ + "transform-runtime", + { + "polyfill": false, + "regenerator": false + } + ], + "espower" + ] + }, + "require": [ + "babel-register" + ] + }, + "nyc": { + "exclude": [ + "**/*.test.*", + "**/testing/*" + ], + "reporter": [ + "html", + "cobertura" + ] } } diff --git a/packages/@jali/core/index.ts b/packages/@jali/core/index.ts new file mode 100644 index 0000000..72d2ede --- /dev/null +++ b/packages/@jali/core/index.ts @@ -0,0 +1,9 @@ +import * as Iterables from './iterables'; +import * as TypeGuards from './type-guards'; + +export { default as MessagePriority } from './src/message-priority'; +export { default as MessageSeverity } from './src/message-severity'; +export { default as NotificationMessage } from './src/notification-message'; +export { default as StructuredError } from './src/structured-error' + +export { Iterables, TypeGuards }; diff --git a/packages/@jali/core/iterables/index.ts b/packages/@jali/core/iterables/index.ts new file mode 100644 index 0000000..768551c --- /dev/null +++ b/packages/@jali/core/iterables/index.ts @@ -0,0 +1 @@ +export * from '../src/notification-message-iterables' diff --git a/packages/@jali/core/package.json b/packages/@jali/core/package.json new file mode 100644 index 0000000..b2e1414 --- /dev/null +++ b/packages/@jali/core/package.json @@ -0,0 +1,53 @@ +{ + "//": [ + "Node Package Manager (NPM) package definition file for the @jail/core package", + "See https://docs.npmjs.com/files/package.json" + ], + "name": "@jali/core", + "version": "0.0.1-prealpha.1", + "description": "Core service and application level utlities for the Jali microservice platform.", + "keywords": [ + "jali", + "jalijs", + "jalims", + "microservice", + "microservices", + "typescript" + ], + "homepage": "http://jali-ms.io/", + "bugs": { + "url": "https://github.com/latticework/jali/issues" + }, + "license": "MIT", + "author": "Latticework (https://medium.com/@latticeworkms)", + "contributors": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "maintainers": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "main": "bundles/util.umd.js", + "repository": { + "type": "git", + "url": "https://github.com/latticework/jali.git" + }, + "engines": { + "node": ">= 6.0.0" + }, + "preferGlobal": false, + "private": false, + "publishConfig": { + "access": "public", + "tag": "prealpha" + }, + "module": "index.js", + "typings": "index.d.ts" +} diff --git a/packages/@jali/core/src/message-priority.ts b/packages/@jali/core/src/message-priority.ts new file mode 100644 index 0000000..46f2334 --- /dev/null +++ b/packages/@jali/core/src/message-priority.ts @@ -0,0 +1,24 @@ +/** + * Represents the priority of notification messages. + */ +enum MessagePriority { + /** Specifies that a message must be communicated. */ + Mandatory = 0, + + /** Specifies high priority messages such as errors. */ + High = 1, + + /** Specifies normal priority messages such as warnings. */ + Normal = 2, + + /** Specifies low priority messages such as information messages. */ + Low = 3, + + /** Specifies very low priority messages such as debug messages. */ + VeryLow = 4, + + /** Specifies messages which should not be communicated, such as trace messages. */ + Restricted = 5, +} + +export default MessagePriority; diff --git a/packages/@jali/core/src/message-severity.ts b/packages/@jali/core/src/message-severity.ts new file mode 100644 index 0000000..771143b --- /dev/null +++ b/packages/@jali/core/src/message-severity.ts @@ -0,0 +1,45 @@ +// tslint:disable:max-line-length +/** + * Represents the severity of a notification message. + * @see [Microsoft.Extensions.Logging.LogLevel]{@link https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Abstractions/LogLevel.cs} + */ +enum MessageSeverity { +// tslint:enable:max-line-length + /** + * Logs that describe an unrecoverable application or system crash, or a catastrophic failure + * that requires immediate attention. + */ + Critical = 0, + + /** + * Logs that highlight when the current flow of execution is stopped due to a failure. These + * should indicate a failure in the current activity, not an application-wide failure. + */ + Error = 1, + + /** + * Logs that highlight an abnormal or unexpected event in the application flow, but do not + * otherwise cause the application execution to stop. + */ + Warning = 2, + + /** + * Logs that track the general flow of the application. These logs should have long-term value. + */ + Information = 3, + + /** + * Logs that are used for interactive investigation during development. These logs should + * primarily contain information useful for debugging and have no long-term value. + */ + Debug = 4, + + /** + * Logs that contain the most detailed messages. These messages may contain sensitive + * application data. These messages are disabled by default and should never be enabled in a + * production environment. + */ + Trace = 5, +} + +export default MessageSeverity; diff --git a/packages/@jali/core/src/notification-message-iterables.ts b/packages/@jali/core/src/notification-message-iterables.ts new file mode 100644 index 0000000..11023b7 --- /dev/null +++ b/packages/@jali/core/src/notification-message-iterables.ts @@ -0,0 +1,18 @@ +import { Errors, Iterables } from '@jali/util'; + +import MessageSeverity from './message-severity'; +import NotificationMessage from './notification-message'; + +export function error(messages: Iterable): + NotificationMessage | undefined { + Errors.verifyIterable('messages', messages); + + return Iterables.find(get_Errors(messages)); +} + +export function get_Errors(messages: Iterable): + Iterable { + Errors.verifyIterable('messages', messages); + + return Iterables.filter(messages, message => message.severity <= MessageSeverity.Error); +} diff --git a/packages/@jali/core/src/notification-message.ts b/packages/@jali/core/src/notification-message.ts new file mode 100644 index 0000000..9460508 --- /dev/null +++ b/packages/@jali/core/src/notification-message.ts @@ -0,0 +1,14 @@ +import MessagePriority from './message-priority'; +import MessageSeverity from './message-severity'; + +interface NotificationMessage { + readonly messageCode: string; + readonly priority: MessagePriority; + readonly severity: MessageSeverity; + readonly message: string; + readonly args?: Object; + readonly objectKey?: string; + readonly propertyNames?: string[]; +} + +export default NotificationMessage; diff --git a/packages/@jali/core/src/structured-error.ts b/packages/@jali/core/src/structured-error.ts new file mode 100644 index 0000000..a009a59 --- /dev/null +++ b/packages/@jali/core/src/structured-error.ts @@ -0,0 +1,57 @@ +import { TypeGuards as UtilTypeGuards } from '@jali/util'; + +import * as TypeGuards from '../type-guards'; +import * as Iterables from '../iterables'; + +import NotificationMessage from './notification-message'; + +export default class StructuredError extends Error { + public readonly innerError: Error | undefined; + + constructor() + constructor(message: NotificationMessage) + constructor(messages: Iterable) + constructor(innerError: Error) + constructor(message: NotificationMessage, innerError: Error) + constructor(messages: Iterable, innerError: Error) + /** + * @todo ensure meaningful {@link NotificationMessage}. + */ + constructor( + messageOrMessagesOrError: + NotificationMessage | Iterable | Error | undefined = undefined, + innerError: Error | undefined = undefined) { + super(Class.resolveMessage(messageOrMessagesOrError)); // Also validates. + + + this.innerError = innerError; + + if (UtilTypeGuards.isError(messageOrMessagesOrError)) { + this.innerError = messageOrMessagesOrError; + } + + } + + /** + * Verifies the first constructor parameter and Resolves an error message from it for the + * {@link Error} constructor. + */ + private static resolveMessage( + messageOrMessagesOrError: + NotificationMessage | Iterable | Error | undefined = undefined): + string | undefined { + if (TypeGuards.isNotificationMessage(messageOrMessagesOrError)) { + return messageOrMessagesOrError.message; + } else if (UtilTypeGuards.makeIsIterable(TypeGuards.isNotificationMessage)( + messageOrMessagesOrError)) { + let error = Iterables.error(messageOrMessagesOrError); + + if (error !== undefined) { + return error.message; + } + } + + return undefined; + } +} +const Class = StructuredError; // tslint:disable-line:variable-name diff --git a/packages/@jali/core/src/type-guards.ts b/packages/@jali/core/src/type-guards.ts new file mode 100644 index 0000000..bbd6acf --- /dev/null +++ b/packages/@jali/core/src/type-guards.ts @@ -0,0 +1,10 @@ +import NotificationMessage from './notification-message'; + +export function isNotificationMessage(arg: any): arg is NotificationMessage { + const message = arg as NotificationMessage; + + return message.messageCode !== undefined + && message.priority !== undefined + && message.severity !== undefined + && message.message !== undefined; +} diff --git a/packages/@jali/core/tsconfig-build.json b/packages/@jali/core/tsconfig-build.json new file mode 100644 index 0000000..8b6b657 --- /dev/null +++ b/packages/@jali/core/tsconfig-build.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "inlineSources": true, + "lib": ["es2017"], + "module": "es2015", + "moduleResolution": "node", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "../../../dist/packages-dist/@jali/core/", + "paths": { + "@jali/*": ["../../../dist/packages-dist/@jali/*"] + }, + "rootDir": ".", + "sourceMap": true, + "strictNullChecks": true, + "stripInternal": true, + "target": "es6" + }, + "files": [ + "index.ts", + "iterables/index.ts", + "type-guards/index.ts" + ] +} + + diff --git a/packages/@jali/core/type-guards/index.ts b/packages/@jali/core/type-guards/index.ts new file mode 100644 index 0000000..1b38fbf --- /dev/null +++ b/packages/@jali/core/type-guards/index.ts @@ -0,0 +1 @@ +export * from '../src/type-guards'; diff --git a/packages/@jali/note/es2015.tsconfig.json b/packages/@jali/note/es2015.tsconfig.json new file mode 100644 index 0000000..9ee3b1d --- /dev/null +++ b/packages/@jali/note/es2015.tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "stripInternal": true, + "experimentalDecorators": true, + "module": "es2015", + "moduleResolution": "node", + "outDir": "../../../dist/packages-dist/router/esm", + "paths": { + "@angular/core": ["../../../dist/packages-dist/core"], + "@angular/common": ["../../../dist/packages-dist/common"], + "@angular/compiler": ["../../../dist/packages-dist/compiler"], + "@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"], + "@angular/platform-browser-dynamic": ["../../../dist/packages-dist/platform-browser-dynamic"] + }, + "rootDir": ".", + "sourceMap": true, + "inlineSources": true, + "lib": ["es6", "dom"], + "target": "es6" + }, + "files": [ + "index.ts" + ] +} \ No newline at end of file diff --git a/packages/@jali/note/es5.tsconfig.json b/packages/@jali/note/es5.tsconfig.json new file mode 100644 index 0000000..a239a79 --- /dev/null +++ b/packages/@jali/note/es5.tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "stripInternal": true, + "experimentalDecorators": true, + "module": "commonjs", + "moduleResolution": "node", + "outDir": "../../../dist/packages-dist/router/", + "paths": { + "@angular/core": ["../../../dist/packages-dist/core"], + "@angular/common": ["../../../dist/packages-dist/common"], + "@angular/compiler": ["../../../dist/packages-dist/compiler"], + "@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"], + "@angular/platform-browser-dynamic": ["../../../dist/packages-dist/platform-browser-dynamic"] + }, + "rootDir": ".", + "sourceMap": true, + "inlineSources": true, + "lib": ["es6", "dom"], + "target": "es5" + }, + "files": [ + "index.ts" + ] +} diff --git a/packages/@jali/note/index.ts b/packages/@jali/note/index.ts new file mode 100644 index 0000000..03b2590 --- /dev/null +++ b/packages/@jali/note/index.ts @@ -0,0 +1,8 @@ +export { default as MessageCode } from './src/message-code'; +export { default as MessageEncoding } from './src/message-encoding'; +export { default as MessageEncodingData } from './src/message-encoding-data'; +export { default as MessageEncodingSegmentData } from './src/message-encoding-segment-data'; +export { default as MessageEncodingVersion } from './src/message-encoding-version'; +export { default as StandardMessageEncoding } from './src/standard-message-encoding'; +export { default as StandardMessageEncodingVersion } from './src/standard-message-encoding-version'; +export { default as TypedMessage } from './src/typed-message'; diff --git a/packages/@jali/note/package.json b/packages/@jali/note/package.json new file mode 100644 index 0000000..8a60026 --- /dev/null +++ b/packages/@jali/note/package.json @@ -0,0 +1,53 @@ +{ + "//": [ + "Node Package Manager (NPM) package definition file for the @jail/note package", + "See https://docs.npmjs.com/files/package.json" + ], + "name": "@jali/note", + "version": "0.0.1-prealpha.1", + "description": "Common jali notification message definitions.", + "keywords": [ + "jali", + "jalijs", + "jalims", + "microservice", + "microservices", + "typescript" + ], + "homepage": "http://jali-ms.io/", + "bugs": { + "url": "https://github.com/latticework/jali/issues" + }, + "license": "MIT", + "author": "Latticework (https://medium.com/@latticeworkms)", + "contributors": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "maintainers": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "main": "bundles/util.umd.js", + "repository": { + "type": "git", + "url": "https://github.com/latticework/jali.git" + }, + "engines": { + "node": ">= 6.0.0" + }, + "preferGlobal": false, + "private": false, + "publishConfig": { + "access": "public", + "tag": "prealpha" + }, + "module": "index.js", + "typings": "index.d.ts" +} diff --git a/packages/@jali/note/src/message-code.md b/packages/@jali/note/src/message-code.md new file mode 100644 index 0000000..0f7a7a1 --- /dev/null +++ b/packages/@jali/note/src/message-code.md @@ -0,0 +1,51 @@ +## Schema 0: PRIVATE + +``` +0123 4567 +|||| | +CVPS BBBB +|||| | Pos From To Segment Name +|||| | --- ---- ---- ---------------------- +|||| +--- 4 0000 - FFFF Base Code +|||+----- 3 0 - F Severity +||+------ 2 0 - F Priority +|+-------- 1 0 - F Schema Version ++--------- 0 0 Schema +``` + +## Schema 1: STANDARD +``` + 1 +0123 4567 8901 2345 +||| | | || | +CVAA AADD LLPS BBBB +||| | | || | Pos From To Segment Name +||| | | || | --- ---- ---- ---------------------- +||| | | || +--- 12 0000 - FFFF Base Code +||| | | |+----- 11 0 - F Severity +||| | | +------ 10 0 - F Priority +||| | +-------- 8 00 - FF Library +||| +----------- 6 00 - FF Domain +||+---------------- 2 0000 - FFFF Authority (Registered) +|+----------------- 1 0 F Schema Version ++------------------ 0 1 Schema +``` + +## Schema 2: EXTENDED +``` + 1 2 3 +0123 4567 8901 2345 6789 0123 4567 8901 +||| | | | | | +CVAA AAAA DDDD DDDD LLLL LLPP SSBB BBBB +||| | | | | | Pos From To Segment Name +||| | | | | | --- --------- --------- ---------------------- +||| | | | | +------ 26 0000 0000 - FF FFFF Base Code +||| | | | +-------- 24 00 - FF Severity +||| | | +----------- 22 00 - FF Priority +||| | +------------------ 16 00 0000 - FF FFFF Library +||| +---------------------------- 8 00 0000 - FF FFFF Domain +||+------------------------------------ 2 00 0000 - FF FFFF Authority (Registered) +|+------------------------------------- 1 0 - F Schema Version ++-------------------------------------- 0 2 Schema + +``` \ No newline at end of file diff --git a/packages/@jali/note/src/message-code.ts b/packages/@jali/note/src/message-code.ts new file mode 100644 index 0000000..6860d0c --- /dev/null +++ b/packages/@jali/note/src/message-code.ts @@ -0,0 +1,25 @@ +import { Errors } from '@jali/util'; + +import { MessagePriority } from '@jali/core'; + +import MessageEncoding from './message-encoding'; +import * as StandardEncodings from './standard-encodings'; + +export default class MessageCode { + public constructor( + public readonly messageCode: string, messageEncoding?: MessageEncoding) { + Errors.verifyString('messageCode', messageCode); + if (messageEncoding) { Errors.verifyObject('messageEncoding', messageEncoding); } + + this.messageEncoding = messageEncoding || StandardEncodings.standard; + + // if (!messageEncoding.isValidCode(messageCode)) { + // } + } + + public get priority(): MessagePriority { + return this.messageEncoding.getMessagePriority(this.messageCode); + } + + protected readonly messageEncoding: MessageEncoding; +} diff --git a/packages/@jali/note/src/message-encoding-data.ts b/packages/@jali/note/src/message-encoding-data.ts new file mode 100644 index 0000000..4c120b0 --- /dev/null +++ b/packages/@jali/note/src/message-encoding-data.ts @@ -0,0 +1,14 @@ +import MessageEncodingSegmentData from './message-encoding-segment-data'; + +interface MessageEncodingData { + readonly schema: number; + readonly schemaVersion: number; + readonly authorityData: MessageEncodingSegmentData; + readonly domainData: MessageEncodingSegmentData; + readonly libraryData: MessageEncodingSegmentData; + readonly priorityData: MessageEncodingSegmentData; + readonly severityData: MessageEncodingSegmentData; + readonly baseMessageCodeData: MessageEncodingSegmentData; +} + +export default MessageEncodingData; diff --git a/packages/@jali/note/src/message-encoding-segment-data.ts b/packages/@jali/note/src/message-encoding-segment-data.ts new file mode 100644 index 0000000..9a7d6d7 --- /dev/null +++ b/packages/@jali/note/src/message-encoding-segment-data.ts @@ -0,0 +1,6 @@ +interface MessageEncodingSegmentData { + readonly position: number; + readonly length: number; +} + +export default MessageEncodingSegmentData; diff --git a/packages/@jali/note/src/message-encoding-version.ts b/packages/@jali/note/src/message-encoding-version.ts new file mode 100644 index 0000000..2587816 --- /dev/null +++ b/packages/@jali/note/src/message-encoding-version.ts @@ -0,0 +1,13 @@ + +interface MessageEncodingVersion { + version: number; + isValidCode(messageCode: string): boolean; + getAuthorityCode(messageCode: string): number; + getDomainCode(messageCode: string): number; + getLibraryCode(messageCode: string): number; + getMessagePriority(messageCode: string): number; + getMessageSeverity(messageCode: string): number; + getBaseMessageCode(messageCode: string): number; +} + +export default MessageEncodingVersion; diff --git a/packages/@jali/note/src/message-encoding.ts b/packages/@jali/note/src/message-encoding.ts new file mode 100644 index 0000000..f55f95e --- /dev/null +++ b/packages/@jali/note/src/message-encoding.ts @@ -0,0 +1,15 @@ +import MessageEncodingVersion from './message-encoding-version'; + +export interface MessageEncoding { + versions: Iterable; + + isValidCode(messageCode: string): boolean; + getAuthorityCode(messageCode: string): number; + getDomainCode(messageCode: string): number; + getLibraryCode(messageCode: string): number; + getMessagePriority(messageCode: string): number; + getMessageSeverity(messageCode: string): number; + getBaseMessageCode(messageCode: string): number; +} + +export default MessageEncoding; diff --git a/packages/@jali/note/src/standard-encodings.ts b/packages/@jali/note/src/standard-encodings.ts new file mode 100644 index 0000000..0848dbc --- /dev/null +++ b/packages/@jali/note/src/standard-encodings.ts @@ -0,0 +1,6 @@ +import StandardMessageEncoding from './standard-message-encoding'; + + +export const local = new StandardMessageEncoding([]); +export const standard = new StandardMessageEncoding([]); +export const extended = new StandardMessageEncoding([]); diff --git a/packages/@jali/note/src/standard-message-encoding-version.ts b/packages/@jali/note/src/standard-message-encoding-version.ts new file mode 100644 index 0000000..144f7aa --- /dev/null +++ b/packages/@jali/note/src/standard-message-encoding-version.ts @@ -0,0 +1,55 @@ +import { Errors, } from '@jali/util'; +import { MessagePriority, MessageSeverity } from '@jali/core'; + +import MessageEncodingVersion from './message-encoding-version'; +import MessageEncodingData from './message-encoding-data'; +import MessageEncodingSegmentData from './message-encoding-segment-data'; + + +export default class StandardMessageEncodingVersion implements MessageEncodingVersion { + public constructor(public readonly data: MessageEncodingData) { + } + + public get version() { return this.data.schemaVersion; } + + public isValidCode(messageCode: string): boolean { + Errors.verifyNonEmpty('messageCode', messageCode); + + throw new Error(); + } + + public getAuthorityCode(messageCode: string): number { + return this.getSegmentValue(messageCode, this.data.authorityData); + } + + public getDomainCode(messageCode: string): number { + return this.getSegmentValue(messageCode, this.data.domainData); + } + + public getLibraryCode(messageCode: string): number { + return this.getSegmentValue(messageCode, this.data.libraryData); + } + + public getMessagePriority(messageCode: string): MessagePriority { + return this.getSegmentValue(messageCode, this.data.priorityData); + } + + public getMessageSeverity(messageCode: string): MessageSeverity { + return this.getSegmentValue(messageCode, this.data.severityData); + } + + public getBaseMessageCode(messageCode: string): number { + return this.getSegmentValue(messageCode, this.data.baseMessageCodeData); + } + + private getSegmentValue(messageCode: string, data: MessageEncodingSegmentData): number { + Errors.verifyNonEmpty('messageCode', messageCode); + Errors.verifyObject('data', data); + + throw new Error(); + // const totalLength = data.position + length; + // if (messageCode.length < totalLength) { + // throw + // } + } +} diff --git a/packages/@jali/note/src/standard-message-encoding.ts b/packages/@jali/note/src/standard-message-encoding.ts new file mode 100644 index 0000000..d60bbc8 --- /dev/null +++ b/packages/@jali/note/src/standard-message-encoding.ts @@ -0,0 +1,87 @@ +import { Errors, Iterables } from '@jali/util'; + +import { MessagePriority, MessageSeverity } from '@jali/core'; + +import MessageEncoding from './message-encoding'; +import MessageEncodingVersion from './message-encoding-version'; +// import MessageEncodingData from './message-encoding-data'; + +export default class StandardMessageEncoding implements MessageEncoding { + public constructor(public readonly versions: Iterable) { + Errors.verifyIterable('versions', versions); + + this.versionMap = createEncodingVersionMap(versions); + } + +// public static standardEncodings: MessageEncoding[] = [ +// new StandardMessageEncoding(createEncodingVersionMap()) +// ]; + + /** + * @todo implement + */ + public isValidCode(messageCode: string): boolean { + Errors.verifyNotWhitespace('messageCode', messageCode); + + throw new Error('Not Implemented.'); + } + + getAuthorityCode(messageCode: string): number { + Errors.verifyNotWhitespace('messageCode', messageCode); + + const version = this.getValidVersion(messageCode); + return version.getAuthorityCode(messageCode); + } + + getDomainCode(messageCode: string): number { + Errors.verifyNotWhitespace('messageCode', messageCode); + + const version = this.getValidVersion(messageCode); + return version.getDomainCode(messageCode); + } + + getLibraryCode(messageCode: string): number { + Errors.verifyNotWhitespace('messageCode', messageCode); + + const version = this.getValidVersion(messageCode); + return version.getLibraryCode(messageCode); + } + + getMessagePriority(messageCode: string): MessagePriority { + Errors.verifyNotWhitespace('messageCode', messageCode); + + const version = this.getValidVersion(messageCode); + return version.getMessagePriority(messageCode); + } + + getMessageSeverity(messageCode: string): MessageSeverity { + Errors.verifyNotWhitespace('messageCode', messageCode); + + const version = this.getValidVersion(messageCode); + return version.getMessageSeverity(messageCode); + } + + getBaseMessageCode(messageCode: string): number { + Errors.verifyNotWhitespace('messageCode', messageCode); + + const version = this.getValidVersion(messageCode); + return version.getBaseMessageCode(messageCode); + } + + private readonly versionMap: Map; + + + /** + * @todo implement + */ + private getValidVersion(messageCode: string): MessageEncodingVersion { + Errors.verifyNotWhitespace('messageCode', messageCode); + + throw new Error('Not Implemented.'); + } +} + +function createEncodingVersionMap(encodingVersions: Iterable): + Map { + return Iterables.toMap(encodingVersions, v => v.version); +} diff --git a/packages/@jali/note/src/typed-message.ts b/packages/@jali/note/src/typed-message.ts new file mode 100644 index 0000000..67b5b01 --- /dev/null +++ b/packages/@jali/note/src/typed-message.ts @@ -0,0 +1,24 @@ +import { MessagePriority, MessageSeverity, NotificationMessage } from '@jali/core'; + +import MessageCode from './message-code'; + +export default class TypedMessage implements NotificationMessage { + public get messageCode(): string { + return this.innerMessageCode.messageCode; + } + + get priority(): MessagePriority { + return this.innerMessageCode.priority; + } + readonly severity: MessageSeverity; + readonly message: string; + readonly args?: Args; + objectKey?: string; + propertyNames?: string[]; + + protected constructor(messageCode: string) { + this.innerMessageCode = new MessageCode(messageCode); + } + + private innerMessageCode: MessageCode; +} diff --git a/packages/@jali/note/tsconfig-build.json b/packages/@jali/note/tsconfig-build.json new file mode 100644 index 0000000..34030fd --- /dev/null +++ b/packages/@jali/note/tsconfig-build.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "inlineSources": true, + "lib": ["es2017"], + "module": "es2015", + "moduleResolution": "node", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "../../../dist/packages-dist/@jali/note/", + "paths": { + "@jali/*": ["../../../dist/packages-dist/@jali/*"] + }, + "rootDir": ".", + "sourceMap": true, + "strictNullChecks": true, + "stripInternal": true, + "target": "es6" + }, + "files": [ + "index.ts" + ] +} + + diff --git a/packages/@jali/package.json b/packages/@jali/package.json new file mode 100644 index 0000000..569b1d1 --- /dev/null +++ b/packages/@jali/package.json @@ -0,0 +1,42 @@ +{ + "author": "Latticework (https://medium.com/@latticeworkms)", + "bugs": { + "url": "https://github.com/latticework/jali/issues" + }, + "contributors": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "engines": { + "node": ">= 6.0.0" + }, + "homepage": "http://jali-ms.io/", + "keywords": [ + "jali", + "jalijs", + "jalims", + "microservice", + "microservices", + "typescript" + ], + "license": "MIT", + "maintainers": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "private": false, + "publishConfig": { + "access": "public", + "tag": "pre-alpha" + }, + "repository": { + "type": "git", + "url": "https://github.com/latticework/jali.git" + } +} diff --git a/packages/@jali/util/.npmignore b/packages/@jali/util/.npmignore new file mode 100644 index 0000000..9603c0a --- /dev/null +++ b/packages/@jali/util/.npmignore @@ -0,0 +1,2 @@ +test +testing diff --git a/packages/@jali/util/README.md b/packages/@jali/util/README.md new file mode 100644 index 0000000..e44c6af --- /dev/null +++ b/packages/@jali/util/README.md @@ -0,0 +1,99 @@ +# Jali Util Package + +[//]: # (Keep lines to 72 characters to leave room for the preview ) +[//]: # (pane. ) + + +This package provides JavaScript language level utilities for the Jali +microservice platform. [jali-ms.io][jali-site] + +Utilities include: + +- function argument [`Error`][mdn-error] types +- argument validator functions +- [`iteration`][mdn-iteration] functions that covers most `Array` + iteration functions and more. +- *TypeScript* [user-defined type guard][ts-typeguard] functions for + fundamental JavaScript types. + +## Getting Started + +Install the package: + +```bash +npm install --save @jali/util +``` + +## Usage + +As a utility package many kinds of functions are provided by a few +modules. Major function types are mentioned below. For detailed +information see the Jali [docs][jali-docs-util]. + +### Module @jali/util/errors + +Provides [`Error`][mdn-error] types and function argument verifiers. + +#### Errors + +A family of argument errors are available, with the class +`ArgumentError` as the base type in addition to the `InvalidStateError`, +which it thrown when object functions are called when in a state not +supported by the operation. + +#### Argument Verifiers + +A group of validation errors that throw the appropriate `Error` when an +argument is invalid. Many verifiers provide runtime verification for +static type checks provided by `TypeScript`. + +### Module @jali/util/iterables + +Provides element iteration for any object that implements the +[iterable][mdn-iteration] pattern. + +#### Example + +```javascript +import Iterables from '@jali/util/iterables'; +const sequence = (function*() { yield 1; yield 2; yield 3; })(); +const filtered = Iterables.filter(sequence, e => e % 2 === 0); +console.log(Iterables.find(sequence)); // Displays: 2 +``` + +### Module @jali/util/type-guards + +Provides type verification functions especially useful for type coercion +in `TypeScript`. + + + +#### Example + + + +```javascript +import TypeGuards from '@jali/util/type-guards'; +function processException(err: any): void { + if (TypeGuards.isError(err)) { + console.log(`An error has occurred: ${err.message}`); + } + else { + console.log(`An error has occurred: ${err.toString()}`); + } +} +``` + +## Contribute + +This package is part of the [monorepo][desc-monorepo] +[jali-srcs][jali-repo]. Please refer to that GitHub repository on how to +contribute to the Jali project. + +[jali-docs-util]: http://jali-ms.io/docs/api/util +[jali-repo]: https://github.com/latticework/jali +[jali-site]: http://jali-ms.io/ +[mdn-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error +[mdn-iteration]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols +[desc-monorepo]: http://www.macwright.org/2016/07/08/lerna-npm-organizations-new-wave-modularity.html +[ts-typeguard]: https://www.typescriptlang.org/docs/handbook/advanced-types.html diff --git a/packages/@jali/util/errors/index.ts b/packages/@jali/util/errors/index.ts new file mode 100644 index 0000000..c19fc7e --- /dev/null +++ b/packages/@jali/util/errors/index.ts @@ -0,0 +1,12 @@ +export { default as ArgumentEmptyStringError } from '../src/argument-empty-string-error'; +export { default as ArgumentError } from '../src/argument-error'; +export { default as ArgumentFalseError } from '../src/argument-false-error'; +export { default as ArgumentFalsyError } from '../src/argument-falsy-error'; +export { default as ArgumentNanError } from '../src/argument-nan-error'; +export { default as ArgumentNullError } from '../src/argument-null-error'; +export { default as ArgumentTypeError } from '../src/argument-type-error'; +export { default as ArgumentUndefinedError } from '../src/argument-undefined-error'; +export { default as ArgumentZeroError } from '../src/argument-zero-error'; +export { default as InvalidStateError } from '../src/invalid-state-error'; + +export * from '../src/argument-verifiers'; diff --git a/packages/@jali/util/index.ts b/packages/@jali/util/index.ts new file mode 100644 index 0000000..b86bdf2 --- /dev/null +++ b/packages/@jali/util/index.ts @@ -0,0 +1,5 @@ +import * as Errors from './errors'; +import * as Iterables from './iterables'; +import * as TypeGuards from './type-guards'; + +export { Errors, Iterables, TypeGuards }; diff --git a/packages/@jali/util/iterables/index.ts b/packages/@jali/util/iterables/index.ts new file mode 100644 index 0000000..147a1e1 --- /dev/null +++ b/packages/@jali/util/iterables/index.ts @@ -0,0 +1 @@ +export * from '../src/iterables'; diff --git a/packages/@jali/util/package.json b/packages/@jali/util/package.json new file mode 100644 index 0000000..4b5ae8c --- /dev/null +++ b/packages/@jali/util/package.json @@ -0,0 +1,63 @@ +{ + "//": [ + "Node Package Manager (NPM) package definition file for the @jail/util package", + "See https://docs.npmjs.com/files/package.json" + ], + "name": "@jali/util", + "version": "0.0.1-prealpha.1", + "description": "Language level utilities for the Jali microservice platform.", + "keywords": [ + "jali", + "jalijs", + "jalims", + "microservice", + "microservices", + "typescript", + "argument", + "arguments", + "error", + "errors", + "iterable", + "iterables", + "iterator", + "iterators", + "typeguard", + "validation" + ], + "homepage": "http://jali-ms.io/", + "bugs": { + "url": "https://github.com/latticework/jali/issues" + }, + "license": "MIT", + "author": "Latticework (https://medium.com/@latticeworkms)", + "contributors": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "maintainers": [ + { + "name": "Kenneth Brubaker", + "email": "kennethbrubaker1968@gmail.com", + "url": "https://medium.com/@kenbrubaker" + } + ], + "main": "bundles/util.umd.js", + "module": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/latticework/jali.git" + }, + "engines": { + "node": ">= 6.9.0" + }, + "preferGlobal": false, + "private": false, + "publishConfig": { + "access": "public", + "tag": "prealpha" + }, + "typings": "index.d.ts" +} diff --git a/packages/@jali/util/src/argument-empty-string-error.ts b/packages/@jali/util/src/argument-empty-string-error.ts new file mode 100644 index 0000000..984b9ff --- /dev/null +++ b/packages/@jali/util/src/argument-empty-string-error.ts @@ -0,0 +1,37 @@ +import { default as ArgumentFalsyError } from './argument-falsy-error'; + +/** + * Represents that an argument erroneously has an empty string value. + * + * Throw this {@link Error} if a parameter must be a non-empty string. + * + * @example The argument for the parameter lastName is an empty string. + * throw new ArgumentEmptyStringError('lastName'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link ArgumentWhitespaceStringError} + * @see {@link verifyNonEmpty} + * @see {@link verifyTruthy} + * @see {@link verifyNotWhitespace} + * @public + * @since 0.0.1 + */ +export default class ArgumentEmptyStringError extends ArgumentFalsyError { + /** + * Initializes a new instance of the {@link ArgumentEmptyStringError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must not be an + * empty string. Yours is empty*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super(name, message || 'Argument must not be an empty string. Yours is empty'); + } +} diff --git a/packages/@jali/util/src/argument-error.ts b/packages/@jali/util/src/argument-error.ts new file mode 100644 index 0000000..c711e4e --- /dev/null +++ b/packages/@jali/util/src/argument-error.ts @@ -0,0 +1,49 @@ + +/** + * Represents that an argument has violated a requirement. + * + * Throw this {@link Error} if a parameter has violated a requirement that can't be represented by + * a more specific argument error. + * + * > **Note:** {@link ArgumentError} is the base class for all the Jali argument errors. + * + * > **Note:** The default message in all Jali argument error classes begin with + * > **Error in argument** if `name` is not specified; otherwise **Error in argument '** + * > *argument-name* **'** is used. If `message` is specified, a colon is prefixed. All Jali + * > subclasses specify a message. + * + * @example The argument for the parameter pairs has an odd number of elements. + * throw new ArgumentError('pairs', `Argument must have an even number of elements. Yours has ` + + * `'${pairs.length}'`); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link ArgumentFalsyError} + * @see {@link verifyArgument} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentError extends Error { + /** + * Initializes a new instance of the {@link ArgumentError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * An optional message. Default is no message. See class documentation for more details.*. + * @public + * @since 0.0.1 + */ + public constructor(name?: string, message?: string) { + super(Class.makeMessage(name, message)); + } + + /** @private */ + private static makeMessage(name?: string, message?: string) { + return `Error in argument${name ? ` '${name}'` : ''}${(message) ? `: ${message}` : ''}`; + } +} +const Class = ArgumentError; // tslint:disable-line:variable-name no-use-before-declare diff --git a/packages/@jali/util/src/argument-false-error.ts b/packages/@jali/util/src/argument-false-error.ts new file mode 100644 index 0000000..1ea4169 --- /dev/null +++ b/packages/@jali/util/src/argument-false-error.ts @@ -0,0 +1,35 @@ +import { default as ArgumentFalsyError } from './argument-falsy-error'; + +/** + * Represents that an argument erroneously has a value of `false`. + * + * Throw this {@link Error} if a parameter must be `true`. + * + * @example The argument for the parameter isValid is `false`. + * throw new ArgumentFalseError('lastName'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link verifyTrue} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentFalseError extends ArgumentFalsyError { + /** + * Initializes a new instance of the {@link ArgumentFalseError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must have a + * truthy value. Yours is 'false'*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super(name, ArgumentFalsyError.makeFalsyTypedMessage(message, 'false')); + } +} diff --git a/packages/@jali/util/src/argument-falsy-error.ts b/packages/@jali/util/src/argument-falsy-error.ts new file mode 100644 index 0000000..b82e450 --- /dev/null +++ b/packages/@jali/util/src/argument-falsy-error.ts @@ -0,0 +1,54 @@ +import { default as ArgumentError } from './argument-error'; + +/** + * Represents that an argument erroneously has a _falsy_ value. + * + * Throw this {@link Error} if a parameter must be _truthy_ and can't be represented by a more + * specific argument error. + * + * @example The argument for the parameter item is falsy. + * throw new ArgumentFalsyError('item'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Definition of falsy (MDN) + * @see {@link verifyDefined} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentFalsyError extends ArgumentError { + /** + * Initializes a new instance of the {@link ArgumentFalsyError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must have a + * truthy value. Yours does not*. + * @public + * @since 0.0.1 + */ + public constructor(name?: string, message?: string) { + super(name, Class.makeFalsyTypedMessage(message)); + } + + /** + * Builds a default error message for subclasses. + * @param {string}: [message] - + * Specified message. Otherwise, a generic message will be created like *Argument must have a + * truthy value. Yours ${type ? `is '${type}'` : 'does not'*. + * @param {string}: [type] - + * value to display in the message. Default is to display no value. + * @protected + * @since 0.0.1 + */ + protected static makeFalsyTypedMessage(message?: string, type?: string) { + return message || + `Argument must have a truthy value. Yours ${type ? `is '${type}'` : 'does not'}`; + } +} +const Class = ArgumentFalsyError; // tslint:disable-line:variable-name no-use-before-declare diff --git a/packages/@jali/util/src/argument-nan-error.ts b/packages/@jali/util/src/argument-nan-error.ts new file mode 100644 index 0000000..204ee68 --- /dev/null +++ b/packages/@jali/util/src/argument-nan-error.ts @@ -0,0 +1,35 @@ +import { default as ArgumentFalsyError } from './argument-falsy-error'; + +/** + * Represents that an argument erroneously has a value of `NaN`. + * + * Throw this {@link Error} if a parameter must be a `number`. + * + * @example The argument for the parameter price is `NaN`. + * throw new ArgumentNanError('price'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link verifyNumber} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentNanError extends ArgumentFalsyError { + /** + * Initializes a new instance of the {@link ArgumentNanError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must have a + * truthy value. Yours is 'NaN'*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super(name, ArgumentFalsyError.makeFalsyTypedMessage(message, 'NaN')); + } +} diff --git a/packages/@jali/util/src/argument-null-error.ts b/packages/@jali/util/src/argument-null-error.ts new file mode 100644 index 0000000..690768b --- /dev/null +++ b/packages/@jali/util/src/argument-null-error.ts @@ -0,0 +1,36 @@ +import { default as ArgumentFalsyError } from './argument-falsy-error'; + +/** + * Represents that an argument erroneously has a value of `null`. + * + * Throw this {@link Error} if a parameter must be an object. + * + * @example The argument for the parameter entity is `null`. + * throw new ArgumentNullError('entity'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link verifyNonNull} + * @see {@link verifyObject} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentNullError extends ArgumentFalsyError { + /** + * Initializes a new instance of the {@link ArgumentNullError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must have a + * non-null value. Yours is 'null'*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super(name, message || `Argument must have a non-null value. Yours is 'null'`); + } +} diff --git a/packages/@jali/util/src/argument-type-error.ts b/packages/@jali/util/src/argument-type-error.ts new file mode 100644 index 0000000..22e5bba --- /dev/null +++ b/packages/@jali/util/src/argument-type-error.ts @@ -0,0 +1,54 @@ +import { default as ArgumentError } from './argument-error'; + +/** + * Represents that an argument has an invalid type or an object with the incorrect structure. + * + * Throw this {@link Error} if a parameter has an invalid type and can't be represented by a more + * specific argument error. + * + * @example The argument for the parameter motor is missing a property. + * throw new ArgumentTypeError( + * 'motor', `The argument is not a valid 'Motor'. It lacks the 'start' method`); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link verifyDefined} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentTypeError extends ArgumentError { + /** + * Initializes a new instance of the {@link ArgumentTypeError} class. + * + * @param {!string} type - + * The expected parameter type. + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must have type + * '${type}'. Yours is not*. + * @public + * @since 0.0.1 + */ + constructor(type: string, name?: string, message?: string) { + super(name, Class.makeTypeMessage(type, message)); + } + + /** + * Builds a default error message for subclasses. + * @param {!string}: type - + * The expected parameter type. + * @param {string}: [message] - + * Specified message. Otherwise, a generic message will be created like *Argument must have + * type '${type}'. Yours is not*. + * @protected + * @since 0.0.1 + */ + protected static makeTypeMessage(type: string, message?: string) { + return message || `Argument must have type '${type}'. Yours is not`; + } +} +const Class = ArgumentTypeError; // tslint:disable-line:variable-name no-use-before-declare diff --git a/packages/@jali/util/src/argument-undefined-error.ts b/packages/@jali/util/src/argument-undefined-error.ts new file mode 100644 index 0000000..390a42b --- /dev/null +++ b/packages/@jali/util/src/argument-undefined-error.ts @@ -0,0 +1,35 @@ +import { default as ArgumentFalsyError } from './argument-falsy-error'; + +/** + * Represents that an argument erroneously is 'undefined'. + * + * Throw this {@link Error} if a parameter must have a value. + * + * @example The argument for the parameter element is undefined. + * throw new ArgumentUndefinedError('element'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link verifyDefined} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentUndefinedError extends ArgumentFalsyError { + /** + * Initializes a new instance of the {@link ArgumentUndefinedError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must be + * defined. Yours is 'undefined*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super(name, message || `Argument must be defined. Yours is 'undefined'`); + } +} diff --git a/packages/@jali/util/src/argument-verifiers.ts b/packages/@jali/util/src/argument-verifiers.ts new file mode 100644 index 0000000..8203f3f --- /dev/null +++ b/packages/@jali/util/src/argument-verifiers.ts @@ -0,0 +1,719 @@ +import * as TypeGuards from './type-guards'; + +import ArgumentEmptyStringError from './argument-empty-string-error'; +import ArgumentError from './argument-error'; +import ArgumentFalseError from './argument-false-error'; +import ArgumentFalsyError from './argument-falsy-error'; +import ArgumentNanError from './argument-nan-error'; +import ArgumentNullError from './argument-null-error'; +import ArgumentTypeError from './argument-type-error'; +import ArgumentUndefinedError from './argument-undefined-error'; +import ArgumentWhitespaceStringError from './argument-whitespace-string-error'; +import ArgumentZeroError from './argument-zero-error'; + +/** + * Throws an error if the specified argument value does not pass the specified test. + * + * @param T - + * The `value` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!string} name - + * The formal parameter name. + * @param {T} value - + * The function argument. + * @param {!function(value: T) => boolean} test - + * Evaluates whether the value meets expectations. + * @param {(string | function(value: string): string)} [message] - + * Optional custom message or message factory. + * + * @throws {ArgumentError} + * The test failed. + * + * @example verify that parameter deposit is non-negative + * verifyArgument('deposit', deposit, arg => arg > 0.0); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors, examples β‘£ & β‘€ + * @see {@link ArgumentError} + * @see {@link verifyDefined} + * @see {@link verifyTruthy} + * @since 0.0.1 + */ +export function verifyArgument( + name: string, + value: T, + test: (value: T) => boolean, + message?: string | ((value: T) => string)): void | never { + if (!test(value)) { + throw new ArgumentError(name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument is not an `Array`. + * + * > **Note:** Calls {@link Array.isArray}. + * + * @param T - + * The `element` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!string} name - + * The formal parameter name. + * @param {T} value - + * The function argument. + * @param {(string | function(value: string): string)} [message] - + * Optional custom message or message factory. + * + * @throws {ArgumentUndefinedError} + * The argument is `undefined`. + * @throws {ArgumentTypeError} + * The argument is not an `Array`. + * + * @example verify that parameter collection is an Array + * verifyArray('collection', collection); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link isIterable} + * @see {@link verifyIterable} + * @since 0.0.1 + */ +export function verifyArray( + name: string, value: T[], message?: string | ((value: Iterable) => string)): + void | never { + verifyDefined(name, value, message); + + if (!Array.isArray(value)) { + throw new ArgumentTypeError('Array', name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument is not strictly a boolean value. + * + * > **Note:** If you want to test for _truthy_ values, use {@link verifyTruthy}. + * + * @param {!string} name - + * The formal parameter name. + * @param {boolean} value - + * The function argument. + * @param {(string | function(value: string): string)} [message] - + * Optional custom message or message factory. + * + * @throws {ArgumentUndefinedError} + * The argument is `undefined`. + * @throws {ArgumentTypeError} + * The argument is not `boolean`. + * + * @example verify that parameter isValid is boolean + * verifyBoolean('isValid', isValid); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentTypeError} + * @see {@link ArgumentUndefinedError} + * @see {@link verifyDefined} + * @see {@link verifyTruthy} + * @since 0.0.1 + */ +export function verifyBoolean( + name: string, value: boolean, message?: string | ((value: boolean) => string)): + void | never { + verifyDefined(name, value, message); + + if (typeof value !== 'boolean') { + throw new ArgumentTypeError('boolean', name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument is `undefined`. + * + * > **Note:** If you want to test for _truthy_ values, use {@link verifyTruthy}. + * + * @param T - + * The `value` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!string} name - + * The formal parameter name. + * @param {T} value - + * The function argument. + * @param {(string | function(value: string): string)} [message] - + * Optional custom message or message factory. + * + * @throws {ArgumentUndefinedError} + * The argument is `undefined`. + * + * @example verify that parameter element is defined + * verifyDefined('element', element); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link verifyTruthy} + * @since 0.0.1 + */ +export function verifyDefined( + name: string, value: T, message?: string | ((value: T) => string)): void | never { + if (value === undefined) { + throw new ArgumentUndefinedError( + name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument is not strictly a function expression. + * + * > **Note:** If you want to test for _truthy_ values, use {@link verifyTruthy}. + * + * @param {!string} name - + * The formal parameter name. + * @param {Function} value - + * The function argument. + * @param {(string | function(value: string): string)} [message] - + * Optional custom message or message factory. + * + * @throws {ArgumentUndefinedError} + * The argument is `undefined`. + * @throws {ArgumentTypeError} + * The argument is not a `function`. + * + * @example verify that parameter factory is a function + * verifyFunction('factory', factory); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentTypeError} + * @see {@link ArgumentUndefinedError} + * @see {@link verifyDefined} + * @see {@link verifyTruthy} + * @since 0.0.1 + */ +export function verifyFunction( + name: string, value: Function, message?: string | ((value: Function) => string)): void | never { + verifyDefined(name, value, message); + + if (typeof value !== 'function') { + throw new ArgumentTypeError('function', name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument does not support iteration. + * + * > **Note:** Calls {@link isIterable} to determine iterability. + * + * @param T - + * The `element` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!string} name - + * The formal parameter name. + * @param {T} value - + * The function argument. + * @param {(string | function(value: string): string)} [message] - + * Optional custom message or message factory. + * + * @throws {ArgumentUndefinedError} + * The argument is `undefined`. + * @throws {ArgumentTypeError} + * The argument does not support iteration. + * + * @example verify that parameter collection is iterable + * verifyIterable('collection', collection); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link isIterable} + * @see {@link verifyArray} + * @since 0.0.1 + */ +export function verifyIterable( + name: string, value: Iterable, message?: string | ((value: Iterable) => string)): + void | never { + verifyDefined(name, value, message); + + if (!TypeGuards.isIterable(value)) { + throw new ArgumentTypeError('iterable', name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument value is not a non-empty string. + * + * @param {string} name - + * the formal parameter name + * @param {string} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `string`. + * @throws {ArgumentEmptyStringError} + * the argument is an empty `string`. + * + * @example verify that parameter firstName is a non-empty string + * verifyNonEmpty('firstName', firstName); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors, example β‘‘ + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentEmptyStringError} + * @see {@link verifyDefined} + * @see {@link verifyString} + * @see {@link verifyNotWhitespace} + * @since 0.0.1 + */ +export function verifyNonEmpty( + name: string, value: string, message?: string | ((value: string) => string)): void | never { + verifyString(name, value, message); + + if (value === '') { + throw new ArgumentEmptyStringError(name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument value is not a non-zero number. + * + * @param {string} name - + * the formal parameter name + * @param {number} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `number`. + * @throws {ArgumentNanError} + * the argument is `NaN`. + * @throws {ArgumentZeroError} + * the argument is a number the value zero. + * + * @example verify that parameter height has a nonzero value + * verifyNonEmpty('height', height); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentNanError} + * @see {@link ArgumentZeroError} + * @see {@link verifyDefined} + * @see {@link verifyNumber} + * @since 0.0.1 + */ +export function verifyNonZero( + name: string, value: number, message?: string | ((value: number) => string)): void | never { + verifyNumber(name, value, message); + + if (value === 0) { + throw new ArgumentZeroError(name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument value is `undefined` or `null`. + * + * > **Note:** Consider using {@link verifyTruthy} or {@link verifyObject}. + * + * @param {string} name - + * the formal parameter name + * @param {number} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `number`. + * @throws {ArgumentNanError} + * the argument is `NaN`. + * @throws {ArgumentZeroError} + * the argument is a number the value zero. + * + * @example verify that parameter height has a nonzero value + * verifyNonEmpty('height', height); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors, example β‘  + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentNanError} + * @see {@link ArgumentZeroError} + * @see {@link verifyDefined} + * @see {@link verifyNumber} + * @since 0.0.1 + */ +export function verifyNotNull( + name: string, value: T, message?: string | ((value: T) => string)): void | never { + verifyDefined(name, value, message); + + if (value === null) { + throw new ArgumentNullError(name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument is not a string with non whitespace characters. + * + * @param {string} name - + * the formal parameter name + * @param {string} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `string`. + * @throws {ArgumentEmptyStringError} + * the argument is an empty `string`. + * @throws {ArgumentWhitespaceStringError} + * the argument has only whitespace characters. + * + * @example verify that parameter firstName has non-whitespace characters + * verifyNotWhitespace('firstName', firstName); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors, example β‘‘ + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentEmptyStringError} + * @see {@link ArgumentWhitespaceStringError} + * @see {@link verifyDefined} + * @see {@link verifyString} + * @see {@link verifyNonEmpty} + * @since 0.0.1 + */ +export function verifyNotWhitespace( + name: string, value: string, message?: string | ((value: string) => string)): void | never { + verifyNonEmpty(name, value, message); + + if (value.trim() === '') { + throw new ArgumentWhitespaceStringError(name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument value is not a `number` or has a value of `NaN`. + * + * @param {string} name - + * the formal parameter name + * @param {number} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `number`. + * @throws {ArgumentNanError} + * the argument is `NaN`. + * @throws {ArgumentZeroError} + * the argument is a number the value zero. + * + * @example verify that parameter price is a number + * verifyNumber('price', price); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentNanError} + * @see {@link ArgumentZeroError} + * @see {@link verifyDefined} + * @see {@link verifyNonZero} + * @since 0.0.1 + */ +export function verifyNumber( + name: string, value: number, message?: string | ((value: number) => string)): void | never { + verifyDefined(name, value, message); + + if (typeof value !== 'number') { + throw new ArgumentTypeError('number', name, errorMessage(value, message)); + } + + if (Number.isNaN(value)) { + throw new ArgumentNanError(name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument value is not an `Object`. + * + * > **Note:** To exclude `null` values also call {@link verifyNotNull} + * + * @param {string} name - + * the formal parameter name + * @param {Object} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not an `Object`. + * + * @example verify that parameter height has a nonzero value + * verifyNonEmpty('height', height); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentNanError} + * @see {@link ArgumentZeroError} + * @see {@link verifyDefined} + * @see {@link verifyNonZero} + * @since 0.0.1 + */ +export function verifyObject( + name: string, value: Object, message?: string | ((value: Object) => string)): void | never { + verifyDefined(name, value, message); + + if (typeof value !== 'object') { + throw new ArgumentTypeError('object', name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument value is not a `string`. + * + * > **Note:** To verify a meaningful value consider using {@link verifyNonEmpty} or + * > {@link verifyNotWhitespace}. + * + * @param {string} name - + * the formal parameter name + * @param {string} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `string`. + * + * @example verify that parameter alphabet is a string + * verifyNonEmpty('alphabet', alphabet); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link verifyDefined} + * @see {@link verifyNonEmpty} + * @see {@link verifyNotWhitespace} + * @since 0.0.1 + */ +export function verifyString( + name: string, value: string, message?: string | ((value: string) => string)): void | never { + verifyDefined(name, value, message); + + if (typeof value !== 'string') { + throw new ArgumentTypeError('string', name, errorMessage(value, message)); + } +} + + +/** + * Throws an error if the specified argument value is not a boolean with the value 'true'. + * + * > **Note:** To verify a _truthy_ value, use {@link verifyTruthy}. + * + * @param {string} name - + * the formal parameter name + * @param {boolean} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentUndefinedError} + * the argument is `undefined`. + * @throws {ArgumentTypeError} + * the argument is not a `boolean`. + * @throws {ArgumentFalseError} + * the argument is a number the value zero. + * + * @example verify that parameter isValid is true + * verifyNonEmpty('isValid', isValid); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentTypeError} + * @see {@link ArgumentFalseError} + * @see {@link verifyDefined} + * @see {@link verifyBoolean} + * @see {@link verifyTruthy} + * @since 0.0.1 + */ +export function verifyTrue( + name: string, value: boolean, message?: string | ((value: boolean) => string)): void | never { + verifyBoolean(name, value, message); + + if (value === false) { + throw new ArgumentFalseError(name, errorMessage(value, message)); + } +} + +/** + * Throws an error if the specified argument value is not _truthy_. + * + * The `loose` parameter changes what exception is thrown. If `loose`, then only + * {@link ArgumentFalsyError} is thrown. Otherwise, the exception for the appropriate _falsy_ value + * is thrown. + * + * > **Note:** You can test for any of the _falsy_ values individually using the appropriate + * > `verify...` function. + * + * @param T - + * The `value` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {string} name - + * the formal parameter name + * @param {T} value - + * the function argument + * @param {?(string | function(value: string): string)} message - + * optional custom message or message factory + * + * @throws {ArgumentFalsyError} + * the argument is _falsy_ and `loose` is specified. + * @throws {ArgumentEmptyStringError} + * the argument is an empty `string` and `loose` is not specified. + * @throws {ArgumentFalseError} + * the argument has the value `false` and `loose` is not specified. + * @throws {ArgumentNanError} + * the argument has the value `NaN` and `loose` is not specified. + * @throws {ArgumentNullError} + * the argument has the value `null` and `loose` is not specified. + * @throws {ArgumentUndefinedError} + * the argument is `undefined` and `loose` is not specified. + * @throws {ArgumentUndefinedError} + * the argument is zero and `loose` is not specified. + * + * @example verify that parameter item is truthy + * verifyTruthy('item', item); + * + * @see + * Definition of falsy (MDN) + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see + * Example method jali_util_errors, example β‘’ + * @see {@link ArgumentEmptyStringError} + * @see {@link ArgumentFalseError} + * @see {@link ArgumentNanError} + * @see {@link ArgumentNullError} + * @see {@link ArgumentUndefinedError} + * @see {@link ArgumentZeroError} + * @see {@link verifyDefined} + * @see {@link verifyNonEmpty} + * @see {@link verifyNonZero} + * @see {@link verifyNotNull} + * @see {@link verifyNumber} + * @see {@link verifyTrue} + * @since 0.0.1 + */ +export function verifyTruthy( + name: string, value: T, loose = false, message?: string | ((value: T) => string)): + void | never { + if (!value) { + if (loose) { + throw new ArgumentFalsyError(name, errorMessage(value, message)); + } + + verifyNotNull(name, value, message); // Also checks for defined. + + if (typeof value === 'boolean' ) { + verifyTrue(name, value as any as boolean, errorMessage(value, message)); + } else if (typeof value === 'string' ) { + verifyNonEmpty(name, value as any as string, errorMessage(value, message)); + } else if (typeof value === 'number' ) { + // Also checks for not NaN + verifyNonZero(name, value as any as number, errorMessage(value, message)); + } + } +} + +function errorMessage(value: T, message?: string | ((value: T) => string)) { + return (typeof message === 'function') ? message(value) : message; +} diff --git a/packages/@jali/util/src/argument-whitespace-string-error.ts b/packages/@jali/util/src/argument-whitespace-string-error.ts new file mode 100644 index 0000000..80f5ba9 --- /dev/null +++ b/packages/@jali/util/src/argument-whitespace-string-error.ts @@ -0,0 +1,39 @@ +import { default as ArgumentError } from './argument-error'; + +/** + * Represents that a string argument erroneously has only whitespace characters. + * + * Throw this {@link Error} if a parameter must have non-whitespace content. + * + * @example The string argument for the parameter firstName has only whitespace. + * throw new ArgumentWhitespaceStringError('firstName'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link ArgumentEmptyStringError} + * @see {@link verifyNonEmpty} + * @see {@link verifyNotWhitespace} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentWhitespaceStringError extends ArgumentError { + /** + * Initializes a new instance of the {@link ArgumentEmptyStringError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must contain + * non-whitespace characters. Yours has only whitespace*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super( + name, + message || 'Argument must contain non-whitespace characters. Yours has only whitespace'); + } +} diff --git a/packages/@jali/util/src/argument-zero-error.ts b/packages/@jali/util/src/argument-zero-error.ts new file mode 100644 index 0000000..26ef775 --- /dev/null +++ b/packages/@jali/util/src/argument-zero-error.ts @@ -0,0 +1,35 @@ +import { default as ArgumentFalsyError } from './argument-falsy-error'; + +/** + * Represents that an argument erroneously has a value of zero. + * + * Throw this {@link Error} if a parameter must have a non-zero value. + * + * @example The argument for the parameter height is zero. + * throw new ArgumentZeroError('height'); + * + * @see + * package @jali/util + * @see + * module @jali/util/errors + * @see {@link verifyNonZero} + * @see {@link verifyTruthy} + * @public + * @since 0.0.1 + */ +export default class ArgumentZeroError extends ArgumentFalsyError { + /** + * Initializes a new instance of the {@link ArgumentZeroError} class. + * + * @param {string} [name] - + * The parameter name. Default is no name. + * @param {string} [message] - + * Specified message. Otherwise, a generic message will be used like *Argument must have a + * truthy value. Yours is 'zero'*. + * @public + * @since 0.0.1 + */ + constructor(name?: string, message?: string) { + super(name, ArgumentFalsyError.makeFalsyTypedMessage(message, 'zero')); + } +} diff --git a/packages/@jali/util/src/invalid-state-error.ts b/packages/@jali/util/src/invalid-state-error.ts new file mode 100644 index 0000000..50dc955 --- /dev/null +++ b/packages/@jali/util/src/invalid-state-error.ts @@ -0,0 +1,10 @@ +export default class InvalidStateError extends Error { + constructor(message?: string) { + super(Class.makeMessage(message)); + } + + private static makeMessage(specified?: string) { + return specified || `Function called against data in an invalid state.`; + } +} +const Class = InvalidStateError; // tslint:disable-line:variable-name no-use-before-declare diff --git a/packages/@jali/util/src/iterables.ts b/packages/@jali/util/src/iterables.ts new file mode 100644 index 0000000..f72cae2 --- /dev/null +++ b/packages/@jali/util/src/iterables.ts @@ -0,0 +1,682 @@ +import * as ArgumentVerifiers from './argument-verifiers'; +import { isIterable, makeIsIterable } from './type-guards'; + +/** + * @typedef {function} ElementTest - + * test performed on elements of a sequence + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!T} element - + * The iteration element + * @param {!number} index - + * The index of the element + * @param {!Iterable} sequence - + * The sequence being iterated + * @return {boolean} - + * A value indicating whether the specified element passed the test. + */ + +/** + * @typedef {function} ElementConverter - + * Converts the input sequence element to an output sequence element. + * @param T - + * The input `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter + * of the function. + * @param U - + * The output `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter + * of the function. + * @param {!T} element - + * The iteration element + * @param {!number} index - + * The index of the element + * @param {!Iterable} sequence - + * The sequence being iterated + * @return {U} - + * The converted value. + */ + +/** + * @typedef {function} ElementAccumulator - + * Aggregates the input sequence elements to an output value. + * @param T - + * The input `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter + * of the function. + * @param U - + * The output `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter + * of the function. + * @param {!U} previousValue - + * For the first execution either an `initialValue`, if specified, or the first element in the + * sequence. Otherwise, an intermediary accumulated value. + * @param {!T} currentValue - + * The iteration element. + * @param {!currentIndex} index - + * The index of the iteration element + * @param {!Iterable} sequence - + * The sequence being iterated + * @return {U} - + * The accumulated value. + */ + + + + +/* tslint:disable:max-line-length */ +/** + * Converts an argument that could either be a value of a type or a sequence of that type to + * an array of that type. + * + * Specify the constructor to specify the iteration type. Use for values, such as strings, that are + * also iterables of another type. + * + * > **Note:** To treat a string as a value rather than a sequence of characters, you must specify + * > the `String` constructor. + * + * > **Note:** To only ensure an iterable value, use {@link asIterable}. + * + * > **Note:** {@link asArray} is different from + * > target="_blank">Array.from or the + * > target="_blank">spread operator, which only convert an iterable to an array. + * > {@link asArray} ensures a value that can be a scalar value or an array of values is converted + * > to an array. + * + * @param T - + * The `value` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {?(T | Iterable)} valueOrSequence - + * A value that could be a value or an Iterable of that value type. + * @param {function (...args: any[]): T} [ctor] - + * Optional constructor for the type being iterated. + * @return {Array} an array of the value type. + * + * @example ensures argument personOrPersons is converted to an array + * const persons = Iterables.asArray(personOrPersons); + * + * @example ensures string argument colorOrColors is converted to an array + * const persons = Iterables.asArray(colorOrColors, String); + * + * @see + * package @jali/util + * @see + * module @jali/util/iterables + * @see + * Example method jali_util_iterators_asarray, examples β‘  & β‘‘ + * @see {@link asIterable} + * @see Array.from (MDN) + * @see spread syntax (MDN) + * @since 0.0.1 + */ +export function asArray(valueOrSequence: T | Iterable | undefined, ctor?: new(...args: any[]) => T): T[] { +/* tslint:enable:max-line-length */ + if (valueOrSequence === undefined) { + return []; + } + + if (typeof ctor !== 'undefined') { + // Quiet Typescript 2.1.beta / VSCode 1.4.0 compiler error. + // tslint:disable:no-shadowed-variable + const localCtor = ctor as new(...args: any[]) => T; + + if (valueOrSequence instanceof localCtor || + typeof valueOrSequence === 'string' && localCtor as any === String) { + return [valueOrSequence]; + } + + const iterableTypeGuard = makeIsIterable(e => e instanceof localCtor, false); + + if (iterableTypeGuard(valueOrSequence)) { + if (Array.isArray(valueOrSequence)) { + return valueOrSequence; + } + + return [...valueOrSequence]; + } + } + + if (Array.isArray(valueOrSequence)) { + return valueOrSequence; + } + + if (isIterable(valueOrSequence)) { + return [...(valueOrSequence as Iterable)]; + } + + return [valueOrSequence]; +} + +/** + * Converts an argument that could either be a value of a type or a sequence of that type to + * a sequence of that type. + * + * Specify the constructor to specify the iteration type. Use for values, such as strings, that are + * also iterables of another type. + * + * > **Note:** To treat a string as a value rather than a sequence of characters, you must specify + * > the `String` constructor. + * + * > **Note:** To ensure an Array value, use {@link toArray}. + * + * @param T - + * The `value` type. **Note:** This is a TypeScript type parameter, not a parameter of the + * function. + * @param {T | Iterable} valueOrSequence - + * A value that could be a value or an Iterable of that value type. + * @param {function (...args: any[]): T} [ctor] - + * Optional constructor for the type being iterated. + * @return {Iterable} + * + * @example ensures argument idOrIds is converted to an iterable + * const ids = Iterables.asIterable(idOrIds); + * + * @see + * package @jali/util + * @see + * module @jali/util/iterables + * @see + * Example method jali_util_iterators_asiterable, examples β‘  & β‘‘ + * @see {@link asArray} + * @since 0.0.1 + */ +export function asIterable( + valueOrSequence: T | Iterable, ctor?: new(...args: any[]) => T): Iterable { + if (typeof valueOrSequence === 'undefined') { + return []; + } + + if (typeof ctor !== 'undefined') { + // Quiet Typescript 2.1.beta / VSCode 1.4.0 compiler error. + // tslint:disable:no-shadowed-variable + const localCtor = ctor as new(...args: any[]) => T; + + if (valueOrSequence instanceof localCtor || + typeof valueOrSequence === 'string' && localCtor as any === String) { + return [valueOrSequence]; + } + + const iterableTypeGuard = makeIsIterable(e => e instanceof localCtor, false); + + if (iterableTypeGuard(valueOrSequence)) { + if (Array.isArray(valueOrSequence)) { + return valueOrSequence; + } + + return [...valueOrSequence]; + } + } + + if (Array.isArray(valueOrSequence)) { + return valueOrSequence; + } + + if (isIterable(valueOrSequence)) { + return [...(valueOrSequence as Iterable)]; + } + + return [valueOrSequence]; +} + +/* tslint:disable:max-line-length */ +/** + * Concatenates a sequence of a type with zero or more other sequences of that type. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {!Iterable[]} items - + * Array of `Iterable` to concatenate. + * @returns {Iterable} - + * A sequence of elements + * + * @see Array#concat + * + * @since 0.0.1 + */ +export function* concat(sequence: Iterable, ...items: Iterable[]): Iterable { +/* tslint:enable:max-line-length */ + ArgumentVerifiers.verifyIterable('sequence', sequence); + ArgumentVerifiers.verifyArray('items', items); + + // In concat, do not check for array since items may also not be arrays. + + for (const element of sequence) { + yield element; + } + + for (const item of items) { + for (const element of item) { + yield element; + } + } +} + +/* tslint:disable:max-line-length */ +/** + * Returns a value indicating whether every element fulfills the specified test. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {!ElementTest} test - + * A function that returns a value indicating whether an element of the sequence fulfills the + * requirement. + * + * @see Array#every + * + * @since 0.0.1 + */ +export function every( + sequence: Iterable, test: (value: T, index?: number, sequence?: Iterable) => boolean) { +/* tslint:enable:max-line-length */ + ArgumentVerifiers.verifyIterable('sequence', sequence); + ArgumentVerifiers.verifyFunction('test', test); + + if (Array.isArray(sequence)) { return (sequence as T[]).every(test); } + + + let index = 0; + for (const element of sequence) { + if (!test(element, index, sequence)) { + return false; + } + + index += 1; + } + + return true; +} + +/* tslint:disable:max-line-length */ +/** + * Returns a subset of the sequence of those elements that pass the specified test. + * + * > **Note:** To return whether a match was found, use {@link includes}. To return the first + * > matching value, use {@link find}. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {!ElementTest} test - + * The filter function + * @return {Iterable} - + * A sequence of elements + * + * @see Array#filter + * @see {@link find} + * @see {@link includes} + * @since 0.0.1 + */ +export function* filter( + sequence: Iterable, test: (element: T, index: number, sequence: Iterable) => boolean): + Iterable { +/* tslint:enable:max-line-length */ + ArgumentVerifiers.verifyIterable('sequence', sequence); + ArgumentVerifiers.verifyFunction('test', test); + + let index = 0; + for (let element of sequence) { + if (test(element, index, sequence)) { + yield element; + } + + index += 1; + } +} + +/* tslint:disable:max-line-length */ +/** + * Returns the first value matching the specified test or `undefined` if no match was found. If no + * test is specified, returns the first element, or `undefined` if the sequence is empty. + * + * > **Note:** To return whether a match was found, use {@link some}. To match a specific element + * > value, use {@link includes}. To return all matching values, use {@link filter}. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {ElementTest} [test] - + * A function that returns a value indicating whether an element of the sequence fulfills the + * requirement. + * @return {T | undefined} - + * The matched element or `undefined` if no match is found. + * + * @see Array#find + * @see {@link filter} + * @see {@link includes} + * @see {@link some} + * @since 0.0.1 + */ +export function find( + sequence: Iterable, test?: (value: T, index: number, sequence: Iterable) => boolean): + T | undefined { +/* tslint:enable:max-line-length */ + ArgumentVerifiers.verifyIterable('sequence', sequence); + if (test) { ArgumentVerifiers.verifyFunction('test', test); }; + + if (!test) { + for (const element of sequence) { + return element; + } + + return undefined; + } + + if (Array.isArray(sequence)) { return (sequence as T[]).find(test); } + + let index = 0; + for (let element of sequence) { + if (test(element, index, sequence)) { + return element; + } + } + + return undefined; +} + +/* tslint:disable:max-line-length */ +/** + * Returns a value indicating whether a match for the specified test was found. + * + * > **Note:** To return the first matching value, use {@link find}. To return all matching values + * > use {@link filter}. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {?T} value - + * The value search for strictly. + * @param {number} [fromIndex] - + * @return {boolean} - + * `true` if the element was found; otherwise, `false`. + * + * @see Array#includes + * @see {@link find} + * @see {@link filter} + * @since 0.0.1 + */ +export function includes( + sequence: Iterable, value: T | undefined | null, fromIndex?: number): boolean { +/* tslint:enable:max-line-length */ + ArgumentVerifiers.verifyIterable('sequence', sequence); + if (fromIndex) { ArgumentVerifiers.verifyNumber('fromIndex', fromIndex); } + + (sequence as T[]).includes(value as T, fromIndex); + + const test = Number.isNaN(value as any) + ? (e: T) => Number.isNaN(e as any) + : (e: T) => e === value; + + const localSequence = fromIndex + ? slice(sequence, fromIndex) + : sequence; + + return some(localSequence, test); +} + +/* tslint:disable:max-line-length */ +/** + * Returns a sequence of elements that are the result of calling the specified converter function on + * each element. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param U - + * The result type of the converter function. NOTE: This is a TypeScript type parameter, not a + * parameter of the function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {!ElementConverter} converter - + * The element conversion function. + * @return {Iterable} - + * A sequence of converted elements. + * + * @see Array#map + * + * @since 0.0.1 + */ +export function* map( + sequence: Iterable, converter: (element: T, index: number, sequence: Iterable) => U): + Iterable { + ArgumentVerifiers.verifyIterable('sequence', sequence); + ArgumentVerifiers.verifyFunction('converter', converter); + + if (Array.isArray(sequence)) { + yield* (sequence as T[]).map(converter); + return; + } + + let index = 0; + for (const element of sequence) { + yield converter(element, index, sequence); + + index += 1; + } +} + +export function reduce( + sequence: Iterable, + accumulator: ( + previousValue: T, + currentValue: T, + currentIndex: number, + sequence: Iterable) => T, + initialValue?: T): T; +export function reduce( + sequence: Iterable, + accumulator: ( + previousValue: U, + currentValue: T, + currentIndex: number, + sequence: Iterable) => U, + initialValue: U): U; +/* tslint:disable:max-line-length */ +/** + * Aggregates a sequence to a single computed element value. + * + * If `initialValue` is specified `accumulator` is called for the first element using the initial + * value. Otherwise, it is called against the second element using the first element as the initial + * value. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param U - + * The accumulator result type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {!ElementAccumulator} accumulator - + * The element aggregation function + * @param {T} [initialValue] - + * Optional initial value; otherwise, first element is used as initial value + * @return {Iterable} - + * A sequence of converted elements + * + * @see Array#reduce + * + * @since 0.0.1 + */ +export function reduce( + sequence: Iterable, + accumulator: ( + previousValue: T | U, + currentValue: T, + currentIndex: number, + sequence: Iterable) => T | U, + initialValue?: T): T | U { + ArgumentVerifiers.verifyIterable('sequence', sequence); + ArgumentVerifiers.verifyFunction('accumulator', accumulator); + + let index = 0; + // Assignment to quiet TypeScript compiler. + let value: T | U = undefined as any as T | U; + for (const element of sequence) { + if (index === 0) { + if (initialValue !== undefined) { + value = accumulator(initialValue, element, index, sequence); + } else { + value = element; + } + } else { + value = accumulator(value, element, index, sequence); + } + index = index + 1; + } + + return value; +} + +/* tslint:disable:max-line-length */ +/** + * Returns a segment of the original sequence. + * + * Skips `begin` elements and takes `end - begin` elements or the rest of the elements if `end` is + * not specified. If begin is past the last element, no elements are returned. If `begin` or `end` + * is a negative value, the sequence is converted to an array and Array#slice is called. + * + * > **Note:** To retrieve the first element, use {@link first} or {@link firstOrDefault}. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a parameter of the + * function. + * @param {!Iterable} sequence - + * The `Iterable` to operate on + * @param {number} [begin] - + * The first element to include. If negative, converts to an array and uses Array#slice + * @param {number} [end] - + * The first element to include. + * + * @see Array#slice + * + * @since 0.0.1 + */ +export function* slice(sequence: Iterable, begin?: number, end?: number): Iterable { + ArgumentVerifiers.verifyIterable('sequence', sequence); + if (begin !== undefined) { ArgumentVerifiers.verifyNumber('begin', begin); } + if (end !== undefined) { ArgumentVerifiers.verifyNumber('end', end); } + + if (Array.isArray(sequence)) { return yield * (sequence as T[]).slice(begin, end); } + + + if (begin < 0 || end < 0) { return [...sequence].slice(begin, end); } + + + let index = 0; + for (const element of sequence) { + if (index >= begin || 0) { + if (end !== undefined && index === end) { break; } + yield element; + } + + index += 1; + } +} + +/* tslint:disable:max-line-length */ +/** + * Returns a value indicating whether any of the elements of a sequence pass the specified test. + * + * > **Note:** To return the first matching value, use {@link find}. To return all matching values, + * > use {@link filter}. To match on equality, use {@link includes}. + * + * @param T - + * The `Iterator` element type. NOTE: This is a TypeScript type parameter, not a + * parameter of the function. + * @param {Iterable} sequence - + * The `Iterable` to operate on + * @param {?ElementTest} [test] - + * If not defined, indicates that the function should test for any existing elements; otherwise, + * a function that indicates whether an element meets a requirement. + * @return {boolean} - + * `true` if an element was found that meets the test; otherwise, `false`; + * @see Array#some + * @see {@link filter} + * @see {@link find} + * @see {@link includes} + * @since 0.0.1 + */ +export function some( + sequence: Iterable, test?: (value: T, index: number, sequence: Iterable) => boolean): boolean { +/* tslint:enable:max-line-length */ + ArgumentVerifiers.verifyIterable('sequence', sequence); + + if (test === undefined) { + for (let element of sequence) { + element = element; // This quiets the compiler error for `noUnusedLocals`. + return true; + } + + return false; + } + + if (Array.isArray(sequence)) { return (sequence as T[]).some(test); } + + let index = 0; + for (let element of sequence) { + if (test(element, index, sequence)) { + return true; + } + + index += 1; + } + + return false; +} + +/** + * Converts a sequence to a {@link Map} using the specified key selector function. + * + * @param TKey - + * The key type for the {@link Map}. NOTE: This is a TypeScript type parameter, not a parameter + * of the function. + * @param TValue - + * The `Iterable` element type and the value type for the {@link Map}. NOTE: This is a TypeScript + * type parameter, not a parameter of the function. + * @param {Iterable} sequence - + * The `Iterable` to operate on + * @param {function(value: TValue): TKey} keySelector - + * The function that retrieves a key for the specified element. + * @return {Map} - + * The new map. + * @since 0.0.1 + */ +export function toMap( + sequence: Iterable, keySelector: (value: TValue) => TKey): Map { + ArgumentVerifiers.verifyIterable('sequence', sequence); + ArgumentVerifiers.verifyFunction('keySelector', keySelector); + + let map = new Map(); + + for (const value of sequence) { + map.set(keySelector(value), value); + } + + return map; +} diff --git a/packages/@jali/util/src/type-guards.ts b/packages/@jali/util/src/type-guards.ts new file mode 100644 index 0000000..98bdbc3 --- /dev/null +++ b/packages/@jali/util/src/type-guards.ts @@ -0,0 +1,52 @@ +import * as ArgumentVerifiers from './argument-verifiers'; + +import * as Iterables from './iterables'; + +export function isError(value: any): value is Error { + return (value as Error).message !== undefined; +} + +export function makeIsIterable( + elementTypeGuard: (element: any) => boolean, deep?: boolean): + (value: any) => value is Iterable; +export function makeIsIterable( + elementTest: (element: any) => boolean, deep?: boolean): (value: any) => value is Iterable; +export function makeIsIterable( + elementTypeGuard: any, deep = false): + (value: any) => value is Iterable { + ArgumentVerifiers.verifyFunction('elementTypeGuard', elementTypeGuard); + + const test = (value: any) => { + if (!isIterable(value)) { + return false; + } + + if (!deep) { + const first = Iterables.find(value); + + if (first === undefined) { + return true; + } + + return elementTypeGuard(first); + } + + for (const element of value as Iterable) { + if (!elementTypeGuard(element)) { + return false; + } + } + + return true; + }; + + // Need type assertion: See https://github.com/Microsoft/TypeScript/issues/5951. + return test as (value: any) => value is Iterable; +} + + + +export function isIterable(value: any): value is Iterable { + return value && (value as Iterable)[Symbol.iterator] !== undefined; +} + diff --git a/packages/@jali/util/test/argument-empty-string-error.unit.test.ts b/packages/@jali/util/test/argument-empty-string-error.unit.test.ts new file mode 100644 index 0000000..726c639 --- /dev/null +++ b/packages/@jali/util/test/argument-empty-string-error.unit.test.ts @@ -0,0 +1,95 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentEmptyStringError from '../src/argument-empty-string-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentEmptyStringError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentEmptyStringError, + defaultMessage: 'Argument must not be an empty string. Yours is empty', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentEmptyStringError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentEmptyStringError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentEmptyStringError, + defaultMessage: 'Argument must not be an empty string. Yours is empty', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-error.unit.test.ts b/packages/@jali/util/test/argument-error.unit.test.ts new file mode 100644 index 0000000..fc9cb0a --- /dev/null +++ b/packages/@jali/util/test/argument-error.unit.test.ts @@ -0,0 +1,96 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentError from '../src/argument-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentEmptyStringError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentError, + defaultMessage: '', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentError, + defaultMessage: '', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-false-error.unit.test.ts b/packages/@jali/util/test/argument-false-error.unit.test.ts new file mode 100644 index 0000000..f4b6c82 --- /dev/null +++ b/packages/@jali/util/test/argument-false-error.unit.test.ts @@ -0,0 +1,95 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentFalseError from '../src/argument-false-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentFalseError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalseError, + defaultMessage: 'Argument must have a truthy value. Yours is \'false\'', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalseError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalseError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalseError, + defaultMessage: 'Argument must have a truthy value. Yours is \'false\'', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-falsy-error.unit.test.ts b/packages/@jali/util/test/argument-falsy-error.unit.test.ts new file mode 100644 index 0000000..2edd207 --- /dev/null +++ b/packages/@jali/util/test/argument-falsy-error.unit.test.ts @@ -0,0 +1,95 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentFalsyError from '../src/argument-falsy-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentFalsyError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalsyError, + defaultMessage: 'Argument must have a truthy value. Yours does not', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalsyError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalsyError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentFalsyError, + defaultMessage: 'Argument must have a truthy value. Yours does not', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-nan-error.unit.test.ts b/packages/@jali/util/test/argument-nan-error.unit.test.ts new file mode 100644 index 0000000..fee550f --- /dev/null +++ b/packages/@jali/util/test/argument-nan-error.unit.test.ts @@ -0,0 +1,95 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentNanError from '../src/argument-nan-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentNanError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNanError, + defaultMessage: 'Argument must have a truthy value. Yours is \'NaN\'', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNanError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNanError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNanError, + defaultMessage: 'Argument must have a truthy value. Yours is \'NaN\'', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-null-error.unit.test.ts b/packages/@jali/util/test/argument-null-error.unit.test.ts new file mode 100644 index 0000000..2406847 --- /dev/null +++ b/packages/@jali/util/test/argument-null-error.unit.test.ts @@ -0,0 +1,97 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentNullError from '../src/argument-null-error'; + +const DEFAULT_MESSAGE = `Argument must have a non-null value. Yours is 'null'`; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentNullError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNullError, + defaultMessage: DEFAULT_MESSAGE, + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNullError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNullError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentNullError, + defaultMessage: DEFAULT_MESSAGE, + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-type-error.unit.test.ts b/packages/@jali/util/test/argument-type-error.unit.test.ts new file mode 100644 index 0000000..9b307e1 --- /dev/null +++ b/packages/@jali/util/test/argument-type-error.unit.test.ts @@ -0,0 +1,94 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import { testArgumentTypeError, } from '../testing/argument-error-helpers'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentTypeError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + + testArgumentTypeError({ + errorMessage: 'Message', + parameterName: 'Name', + test: t, + type: 'number', + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + + testArgumentTypeError({ + errorMessage: 'Message', + parameterName: 'Name', + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + + testArgumentTypeError({ + errorMessage: 'Message', + parameterName: undefined, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + + testArgumentTypeError({ + errorMessage: undefined, + parameterName: undefined, + test: t, + type: 'number', + }); +}); diff --git a/packages/@jali/util/test/argument-undefined-error.unit.test.ts b/packages/@jali/util/test/argument-undefined-error.unit.test.ts new file mode 100644 index 0000000..041f985 --- /dev/null +++ b/packages/@jali/util/test/argument-undefined-error.unit.test.ts @@ -0,0 +1,97 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentUndefinedError from '../src/argument-undefined-error'; + +const DEFAULT_ARGUMENT_UNDEFINED_ERROR_MESSAGE = + `Argument must be defined. Yours is 'undefined'`; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentUndefinedError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentUndefinedError, + defaultMessage: DEFAULT_ARGUMENT_UNDEFINED_ERROR_MESSAGE, + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentUndefinedError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentUndefinedError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + testArgumentError({ + classConstructor: ArgumentUndefinedError, + defaultMessage: DEFAULT_ARGUMENT_UNDEFINED_ERROR_MESSAGE, + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-verifiers.unit.test.ts b/packages/@jali/util/test/argument-verifiers.unit.test.ts new file mode 100644 index 0000000..3c186dc --- /dev/null +++ b/packages/@jali/util/test/argument-verifiers.unit.test.ts @@ -0,0 +1,4083 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, TestDisposition, } from '../testing'; +import { testArgumentError, testArgumentTypeError, } from '../testing/argument-error-helpers'; + +import * as ArgumentVerifiers from '../src/argument-verifiers'; + +import ArgumentEmptyStringError from '../src/argument-empty-string-error'; +import ArgumentError from '../src/argument-error'; +import ArgumentFalseError from '../src/argument-false-error'; +import ArgumentFalsyError from '../src/argument-falsy-error'; +import ArgumentNanError from '../src/argument-nan-error'; +import ArgumentNullError from '../src/argument-null-error'; +import ArgumentTypeError from '../src/argument-type-error'; +import ArgumentUndefinedError from '../src/argument-undefined-error'; +import ArgumentWhitespaceStringError from '../src/argument-whitespace-string-error'; +import ArgumentZeroError from '../src/argument-zero-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'argument-verifiers'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyArgumentOfT_name_value_test_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyArgumentOfT_name_value_test_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const testResult = true; + const expectedValue = 1; + let actualValue = 0; + const test = (value: number) => { actualValue = value; return testResult; }; + const target = ArgumentVerifiers; + + // act + target.verifyArgument(name, expectedValue, test, message); + + // assert + t.plan(1); + t.true(actualValue === expectedValue, 'test was not called.'); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Smoke, + 'verifyArgumentOfT_name_value_test_message', + 'message-not-specified-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const expectedValue = 1; + const expectedError = new ArgumentError(name, message); + const testResult = false; + let actualValue = 0; + const test = (value: number) => { actualValue = value; return testResult; }; + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArgument(name, expectedValue, test, message); + + // assert + t.plan(4); + const actualError = t.throws(action); + t.true(actualValue === expectedValue, 'test was not called.'); + + testArgumentError({ + classConstructor: ArgumentError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'verifyArgumentOfT_name_value_test_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const testResult = true; + const expectedValue = 1; + let actualValue = 0; + const test = (value: number) => { actualValue = value; return testResult; }; + const target = ArgumentVerifiers; + + // act + target.verifyArgument(name, expectedValue, test, message); + + // assert + t.plan(1); + t.true(actualValue === expectedValue, 'test was not called.'); +}); + + +test( + title( + TestType.Unit, + 'verifyArgumentOfT_name_value_test_message', + 'message-specified-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const expectedValue = 1; + const expectedError = new ArgumentError(name, message); + const testResult = false; + let actualValue = 0; + const test = (value: number) => { actualValue = value; return testResult; }; + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArgument(name, expectedValue, test, message); + + // assert + t.plan(4); + const actualError = t.throws(action); + t.true(actualValue === expectedValue, 'test was not called.'); + + testArgumentError({ + classConstructor: ArgumentError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'verifyArgumentOfT_name_value_test_message', + 'message-function-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const expectedValue = 1; + const expectedError = new ArgumentError(name, message); + const testResult = false; + let actualValue = 0; + const test = (value: number) => { actualValue = value; return testResult; }; + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArgument(name, expectedValue, test, messageFn); + + // assert + t.plan(4); + const actualError = t.throws(action); + t.true(actualValue === expectedValue, 'test was not called.'); + + testArgumentError({ + classConstructor: ArgumentError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyArray_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyArray_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = [1, 2, 3]; + const target = ArgumentVerifiers; + + // act + target.verifyArray(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyArray_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as any[]; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArray(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'Array', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyArray_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = '' as any as any[]; + const expectedError = new ArgumentTypeError('Array', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArray(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'Array', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyArray_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = [1, 2, 3]; + const target = ArgumentVerifiers; + + // act + target.verifyArray(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyArray_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as any[]; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArray(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'Array', + }); +}); + +test( + title( + TestType.Unit, + 'verifyArray_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = '' as any as any[]; + const expectedError = new ArgumentTypeError('Array', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArray(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'Array', + }); +}); + +test( + title( + TestType.Unit, + 'verifyArray_name_value_message', + 'message-function-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = '' as any as any[]; + const expectedError = new ArgumentTypeError('Array', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyArray(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'Array', + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyBoolean_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyBoolean_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyBoolean(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyBoolean_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyBoolean(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyBoolean_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = '' as any as boolean; + const expectedError = new ArgumentTypeError('boolean', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyBoolean(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyBoolean_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyBoolean(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyBoolean_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyBoolean(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +test( + title( + TestType.Unit, + 'verifyBoolean_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = '' as any as boolean; + const expectedError = new ArgumentTypeError('boolean', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyBoolean(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +test( + title( + TestType.Unit, + 'verifyBoolean_name_value_message', + 'message-function-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = '' as any as boolean; + const expectedError = new ArgumentTypeError('boolean', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyBoolean(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyFunction_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyFunction_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = () => {}; + const target = ArgumentVerifiers; + + // act + target.verifyFunction(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyFunction_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as () => {}; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyFunction(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyFunction_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = '' as any as () => {}; + const expectedError = new ArgumentTypeError('function', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyFunction(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyFunction_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = () => {}; + const target = ArgumentVerifiers; + + // act + target.verifyFunction(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyFunction_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as () => {}; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyFunction(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'function', + }); +}); + +test( + title( + TestType.Unit, + 'verifyFunction_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = '' as any as () => {}; + const expectedError = new ArgumentTypeError('function', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyFunction(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'function', + }); +}); + +test( + title( + TestType.Unit, + 'verifyFunction_name_value_message', + 'message-function-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = '' as any as () => {}; + const expectedError = new ArgumentTypeError('function', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyFunction(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'function', + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyDefined_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyDefined_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyDefined(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyDefined_name_value_message', + 'message-not-specified-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyDefined(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentUndefinedError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + + +test( + title( + TestType.Unit, + 'verifyDefined_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyDefined(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyDefined_name_value_message', + 'message-specified-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyDefined(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentUndefinedError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + + +test( + title( + TestType.Unit, + 'verifyDefined_name_value_message', + 'message-function-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyDefined(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentUndefinedError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyIterableOfT_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyIterableOfT_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const value: any[] = []; + const message = undefined; + const target = ArgumentVerifiers; + + // act + target.verifyIterable(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyIterableOfT_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as Iterable; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyIterable(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'iterable', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyIterableOfT_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true as any as Iterable; + const expectedError = new ArgumentTypeError('iterable', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyIterable(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'iterable', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyIterableOfT_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value: any[] = []; + const target = ArgumentVerifiers; + + // act + target.verifyIterable(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyIterableOfT_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as Iterable; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyIterable(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'iterable', + }); +}); + +test( + title( + TestType.Unit, + 'verifyIterableOfT_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true as any as Iterable; + const expectedError = new ArgumentTypeError('iterable', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyIterable(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'iterable', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyIterableOfT_name_value_message', + 'message-function-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = true as any as Iterable; + const expectedError = new ArgumentTypeError('iterable', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyIterable(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'iterable', + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyNonEmpty_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyNonEmpty_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 'non-empty'; + const target = ArgumentVerifiers; + + // act + target.verifyNonEmpty(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as string; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-not-specified-test-fails-for-empty', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = ''; + const expectedError = new ArgumentEmptyStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 'non-empty'; + const target = ArgumentVerifiers; + + // act + target.verifyNonEmpty(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as string; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-specified-test-fails-for-empty', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = ''; + const expectedError = new ArgumentEmptyStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonEmpty_name_value_message', + 'message-function-test-fails-for-empty', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = ''; + const expectedError = new ArgumentEmptyStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonEmpty(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyNonZero_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyNonZero_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 1; + const target = ArgumentVerifiers; + + // act + target.verifyNonZero(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as number; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = '' as any as number; + const expectedError = new ArgumentTypeError('number', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-not-specified-test-fails-for-zero', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 0; + const expectedError = new ArgumentZeroError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 1; + const target = ArgumentVerifiers; + + // act + target.verifyNonZero(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as number; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = '' as any as number; + const expectedError = new ArgumentTypeError('number', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-specified-test-fails-for-zero', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 0; + const expectedError = new ArgumentZeroError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNonZero_name_value_message', + 'message-function-test-fails-for-zero', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = 0; + const expectedError = new ArgumentZeroError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNonZero(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyNotWhitespace_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyNotWhitespace_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 'not-whitespace'; + const target = ArgumentVerifiers; + + // act + target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +test( + title( + TestType.Smoke, + 'verifyNotWhitespace_name_value_message', + 'message-not-specified-test-succeeds-with-some-whitespace'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + // Includes http://www.fileformat.info/info/unicode/char/1680/browsertest.htm + // Includes http://www.fileformat.info/info/unicode/char/2003/index.htm + const value = ' \t\vαš€not-whitespace\u2003\r\n'; + const target = ArgumentVerifiers; + + // act + target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as string; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-not-specified-test-fails-for-empty', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = ''; + const expectedError = new ArgumentEmptyStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-not-specified-test-fails-for-whitespace', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + // Includes http://www.fileformat.info/info/unicode/char/1680/browsertest.htm + // Includes http://www.fileformat.info/info/unicode/char/2003/index.htm + const value = ' \t\vαš€\u2003\r\n'; + const expectedError = new ArgumentWhitespaceStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 'non-empty'; + const target = ArgumentVerifiers; + + // act + target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as string; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-specified-test-fails-for-empty', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = ''; + const expectedError = new ArgumentEmptyStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-specified-test-fails-for-whitespace', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + // Includes http://www.fileformat.info/info/unicode/char/1680/browsertest.htm + // Includes http://www.fileformat.info/info/unicode/char/2003/index.htm + const value = ' \t\vαš€\u2003\r\n'; + const expectedError = new ArgumentWhitespaceStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotWhitespace_name_value_message', + 'message-function-test-fails-for-whitespace', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + // Includes http://www.fileformat.info/info/unicode/char/1680/browsertest.htm + // Includes http://www.fileformat.info/info/unicode/char/2003/index.htm + const value = ' \t\vαš€\u2003\r\n'; + const expectedError = new ArgumentWhitespaceStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotWhitespace(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyNotNull_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyNotNull_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyNotNull(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyNotNull_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotNull(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentUndefinedError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotNull_name_value_message', + 'message-not-specified-test-fails-for-null', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = null as any as boolean; + const expectedError = new ArgumentNullError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotNull(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentNullError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotNull_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyNotNull(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyNotNull_name_value_message', + 'message-specified-test-fails', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotNull(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentUndefinedError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotNull_name_value_message', + 'message-specified-test-fails-for-null', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = null as any as boolean; + const expectedError = new ArgumentNullError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotNull(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentNullError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'verifyNotNull_name_value_message', + 'message-specified-test-fails-for-null', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = null as any as boolean; + const expectedError = new ArgumentNullError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNotNull(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentError({ + classConstructor: ArgumentNullError, + error: actualError, + errorMessage: expectedError.message, + test: t, + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyNumber_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyNumber_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 1; + const target = ArgumentVerifiers; + + // act + target.verifyNumber(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as number; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = '' as any as number; + const expectedError = new ArgumentTypeError('number', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-not-specified-test-fails-for-nan', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = NaN; + const expectedError = new ArgumentNanError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 1; + const target = ArgumentVerifiers; + + // act + target.verifyNumber(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as number; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = '' as any as number; + const expectedError = new ArgumentTypeError('number', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-specified-test-fails-for-nan', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = NaN; + const expectedError = new ArgumentNanError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +test( + title( + TestType.Unit, + 'verifyNumber_name_value_message', + 'message-function-test-fails-for-nan', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = NaN; + const expectedError = new ArgumentNanError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyNumber(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyObject_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyObject_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = {}; + const target = ArgumentVerifiers; + + // act + target.verifyObject(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyObject_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as Object; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyObject(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'object', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyObject_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 1 as any as Object; + const expectedError = new ArgumentTypeError('object', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyObject(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'object', + }); +}); + +test( + title( + TestType.Unit, + 'verifyObject_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = {}; + const target = ArgumentVerifiers; + + // act + target.verifyObject(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyObject_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as Object; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyObject(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'object', + }); +}); + +test( + title( + TestType.Unit, + 'verifyObject_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 1 as any as Object; + const expectedError = new ArgumentTypeError('object', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyObject(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'object', + }); +}); + +test( + title( + TestType.Unit, + 'verifyObject_name_value_message', + 'message-function-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = 1 as any as Object; + const expectedError = new ArgumentTypeError('object', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyObject(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'object', + }); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyString_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyString_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = ''; + const target = ArgumentVerifiers; + + // act + target.verifyString(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyString_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as string; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyString(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyString_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = 1 as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyString(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyString_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = ''; + const target = ArgumentVerifiers; + + // act + target.verifyString(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyString_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as string; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyString(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +test( + title( + TestType.Unit, + 'verifyString_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = 1 as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyString(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyString_name_value_message', + 'message-function-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = 1 as any as string; + const expectedError = new ArgumentTypeError('string', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyString(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyTrue_name_value_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'verifyTrue_name_value_message', + 'message-not-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyTrue(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-not-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-not-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = '' as any as boolean; + const expectedError = new ArgumentTypeError('boolean', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-not-specified-test-fails-for-false', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = undefined; + const value = false; + const expectedError = new ArgumentFalseError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-specified-test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyTrue(name, value, message); + + // assert + t.plan(1); + t.pass(); +}); + + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-specified-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-specified-test-fails-for-wrong-type', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = '' as any as boolean; + const expectedError = new ArgumentTypeError('boolean', name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-specified-test-fails-for-false', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const value = false; + const expectedError = new ArgumentFalseError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + + +test( + title( + TestType.Unit, + 'verifyTrue_name_value_message', + 'message-function-test-fails-for-false', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const message = 'Message'; + const messageFn = () => message; + const value = false; + const expectedError = new ArgumentFalseError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTrue(name, value, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// verifyTruthy_name_value_message + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-succeeds-with-default-parameters'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-succeeds-for-boolean'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-succeeds-for-function'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = () => {}; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-succeeds-for-number'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = 1; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-succeeds-for-object'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = {}; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-succeeds-for-string'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = '1'; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = undefined as any as boolean; + const expectedError = new ArgumentUndefinedError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-fails-for-null', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = null as any as boolean; + const expectedError = new ArgumentNullError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-fails-for-false', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = false; + const expectedError = new ArgumentFalseError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-fails-for-zero', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = 0; + const expectedError = new ArgumentZeroError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-fails-for-nan', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = NaN; + const expectedError = new ArgumentNanError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-not-specified-not-loose-test-fails-for-empty-string', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = false; + const message = undefined; + const value = ''; + const expectedError = new ArgumentEmptyStringError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + +/** ***********************************************************************************************/ +/** ***********************************************************************************************/ + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-succeeds-for-boolean'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = true; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-succeeds-for-function'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = () => {}; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-succeeds-for-number'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = 1; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-succeeds-for-object'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = {}; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-succeeds-for-string'), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = '1'; + const target = ArgumentVerifiers; + + // act + target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(1); + t.pass(); +}); + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-fails-for-undefined', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = undefined as any as boolean; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-fails-for-null', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = null as any as boolean; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-fails-for-false', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = false; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'boolean', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-fails-for-zero', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = 0; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-fails-for-nan', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = NaN; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'number', + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-specified-loose-test-fails-for-empty-string', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const value = ''; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, message); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'verifyTruthy_name_value_message', + 'message-function-loose-test-fails-for-empty-string', + TestDisposition.Negative), + async t => { + await Promise.resolve(); + + // arrange + const name = 'Name'; + const loose = true; + const message = 'Message'; + const messageFn = () => message; + const value = ''; + const expectedError = new ArgumentFalsyError(name, message); + const target = ArgumentVerifiers; + + // act + const action = () => target.verifyTruthy(name, value, loose, messageFn); + + // assert + t.plan(3); + const actualError = t.throws(action) as Error; + + testArgumentTypeError({ + classConstructor: expectedError.constructor, + error: actualError, + errorMessage: expectedError.message, + test: t, + type: 'string', + }); +}); diff --git a/packages/@jali/util/test/argument-whitespace-error.unit.test.ts b/packages/@jali/util/test/argument-whitespace-error.unit.test.ts new file mode 100644 index 0000000..627e379 --- /dev/null +++ b/packages/@jali/util/test/argument-whitespace-error.unit.test.ts @@ -0,0 +1,95 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentWhitespaceStringError from '../src/argument-whitespace-string-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentWhitespaceStringError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentWhitespaceStringError, + defaultMessage: 'Argument must contain non-whitespace characters. Yours has only whitespace', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentWhitespaceStringError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentWhitespaceStringError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentWhitespaceStringError, + defaultMessage: 'Argument must contain non-whitespace characters. Yours has only whitespace', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/argument-zero-error.unit.test.ts b/packages/@jali/util/test/argument-zero-error.unit.test.ts new file mode 100644 index 0000000..6d1f44f --- /dev/null +++ b/packages/@jali/util/test/argument-zero-error.unit.test.ts @@ -0,0 +1,95 @@ +import test from 'ava'; + +import { makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; +import { testArgumentError, } from '../testing/argument-error-helpers'; + +import ArgumentZeroError from '../src/argument-zero-error'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +const title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'ArgumentZeroError'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// constructor_name_message + +////////////// +// Smoke tests + +test( + title( + TestType.Smoke, + 'constructor_name_message', + 'name-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentZeroError, + defaultMessage: 'Argument must have a truthy value. Yours is \'zero\'', + errorMessage: undefined, + parameterName: 'Name', + test: t, + }); +}); + + +////////////// +// Unit tests + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'all-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentZeroError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: 'Name', + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'message-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentZeroError, + defaultMessage: undefined, + errorMessage: 'Message', + parameterName: undefined, + test: t, + }); +}); + +test( + title( + TestType.Unit, + 'constructor_name_message', + 'none-specified'), + async t => { + await Promise.resolve(); + + t.plan(2); + testArgumentError({ + classConstructor: ArgumentZeroError, + defaultMessage: 'Argument must have a truthy value. Yours is \'zero\'', + errorMessage: undefined, + parameterName: undefined, + test: t, + }); +}); diff --git a/packages/@jali/util/test/iterables-as-array.unit.test.ts b/packages/@jali/util/test/iterables-as-array.unit.test.ts new file mode 100644 index 0000000..abf6206 --- /dev/null +++ b/packages/@jali/util/test/iterables-as-array.unit.test.ts @@ -0,0 +1,355 @@ +import test from 'ava'; + +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +import { testAsArray, toIterable } from '../testing/iterables-helpers'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// asArrayOfT_valueOrSequence_ctor + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asArrayOfT_valueOrSequence_ctor', + 'missing-value'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsArray({ + ctor: undefined, + expected: [], + expectingMessage: 'Array should be empty.', + target: Iterables, + test: t, + valueOrSequence: undefined, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asArrayOfT_valueOrSequence_ctor', + 'single-value'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsArray({ + ctor: undefined, + expected: [1], + expectingMessage: 'Array should have one element.', + target: Iterables, + test: t, + valueOrSequence: 1, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asArrayOfT_valueOrSequence_ctor', + 'array-value'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = [1, 2, 3]; + // act + // assert + t.plan(1); + + testAsArray({ + ctor: undefined, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'asArrayOfT_valueOrSequence_ctor', + 'not-array-value'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([1, 2, 3]); + const expectedArray = [...sequence]; + // act + // assert + t.plan(1); + + testAsArray({ + ctor: undefined, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: sequence, + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asArrayOfT_valueOrSequence_ctor', + 'missing-string'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsArray({ + ctor: String, + expected: [], + expectingMessage: 'Array should be empty.', + target: Iterables, + test: t, + valueOrSequence: undefined, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asArrayOfT_valueOrSequence_ctor', + 'single-string'), + async t => { + await Promise.resolve(); + + // arrange + const stringValue = 'abc'; + // act + // assert + t.plan(1); + + testAsArray({ + ctor: String, + expected: [stringValue], + expectingMessage: 'Array should have one element.', + target: Iterables, + test: t, + valueOrSequence: stringValue, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asArrayOfT_valueOrSequence_ctor', + 'string-array-value'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = ['abc', 'def', 'ghi']; + // act + // assert + t.plan(1); + + testAsArray({ + ctor: String, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asArrayOfT_valueOrSequence_ctor', + 'missing-map'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsArray({ + ctor: Map, + expected: [], + expectingMessage: 'Array should be empty.', + target: Iterables, + test: t, + valueOrSequence: undefined, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asArrayOfT_valueOrSequence_ctor', + 'single-map'), + async t => { + await Promise.resolve(); + + // arrange + const mapValue = new Map([[1, 'abc'], [2, 'def'], [3, 'ghi']]); + // act + // assert + t.plan(1); + + testAsArray>({ + ctor: Map, + expected: [mapValue], + expectingMessage: 'Array should have one element.', + target: Iterables, + test: t, + valueOrSequence: mapValue, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asArrayOfT_valueOrSequence_ctor', + 'single-map-no-ctor'), + async t => { + await Promise.resolve(); + + // arrange + const mapValue = new Map([[1, 'abc'], [2, 'def'], [3, 'ghi']]); + // act + // assert + t.plan(1); + + testAsArray>({ + ctor: undefined, + expected: [...mapValue] as any as Map[], + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: mapValue as Map, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asArrayOfT_valueOrSequence_ctor', + 'map-array-value'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = [ + new Map([[1, 'abc'], [2, 'def'], [3, 'ghi']]), + new Map([[4, 'jkl'], [5, 'mno'], [6, 'pqr']]), + new Map([[7, 'stu'], [8, 'vwx'], [9, 'yza']]), + ]; + // act + // assert + t.plan(1); + + testAsArray>({ + ctor: Map, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asArrayOfT_valueOrSequence_ctor', + 'single-string-no-ctor'), + async t => { + await Promise.resolve(); + + // arrange + const stringValue = 'abc'; + // act + // assert + t.plan(1); + + testAsArray({ + ctor: undefined, + expected: [...stringValue], + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: stringValue, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asArrayOfT_valueOrSequence_ctor', + 'string-array-value-no-ctor'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = ['abc', 'def', 'ghi']; + // act + // assert + t.plan(1); + + testAsArray({ + ctor: undefined, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); diff --git a/packages/@jali/util/test/iterables-as-iterable.unit.test.ts b/packages/@jali/util/test/iterables-as-iterable.unit.test.ts new file mode 100644 index 0000000..37de544 --- /dev/null +++ b/packages/@jali/util/test/iterables-as-iterable.unit.test.ts @@ -0,0 +1,354 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; +import { testAsIterable, toIterable } from '../testing/iterables-helpers'; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// asIterableOfT_valueOrSequence_ctor + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asIterableOfT_valueOrSequence_ctor', + 'missing-value'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: undefined, + expected: [], + expectingMessage: 'Array should be empty.', + target: Iterables, + test: t, + valueOrSequence: undefined, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asIterableOfT_valueOrSequence_ctor', + 'single-value'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: undefined, + expected: [1], + expectingMessage: 'Array should have one element.', + target: Iterables, + test: t, + valueOrSequence: 1, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asIterableOfT_valueOrSequence_ctor', + 'array-value'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = [1, 2, 3]; + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: undefined, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'asIterableOfT_valueOrSequence_ctor', + 'not-array-value'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([1, 2, 3]); + const expectedArray = [...sequence]; + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: undefined, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: sequence, + }); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asIterableOfT_valueOrSequence_ctor', + 'missing-string'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: String, + expected: [], + expectingMessage: 'Array should be empty.', + target: Iterables, + test: t, + valueOrSequence: undefined, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asIterableOfT_valueOrSequence_ctor', + 'single-string'), + async t => { + await Promise.resolve(); + + // arrange + const stringValue = 'abc'; + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: String, + expected: [stringValue], + expectingMessage: 'Array should have one element.', + target: Iterables, + test: t, + valueOrSequence: stringValue, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'asIterableOfT_valueOrSequence_ctor', + 'string-array-value'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = ['abc', 'def', 'ghi']; + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: String, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asIterableOfT_valueOrSequence_ctor', + 'missing-map'), + async t => { + await Promise.resolve(); + + // arrange + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: Map, + expected: [], + expectingMessage: 'Array should be empty.', + target: Iterables, + test: t, + valueOrSequence: undefined, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asIterableOfT_valueOrSequence_ctor', + 'single-map'), + async t => { + await Promise.resolve(); + + // arrange + const mapValue = new Map([[1, 'abc'], [2, 'def'], [3, 'ghi']]); + // act + // assert + t.plan(1); + + testAsIterable>({ + ctor: Map, + expected: [mapValue], + expectingMessage: 'Array should have one element.', + target: Iterables, + test: t, + valueOrSequence: mapValue, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asIterableOfT_valueOrSequence_ctor', + 'single-map-no-ctor'), + async t => { + await Promise.resolve(); + + // arrange + const mapValue = new Map([[1, 'abc'], [2, 'def'], [3, 'ghi']]); + // act + // assert + t.plan(1); + + testAsIterable>({ + ctor: undefined, + expected: [...mapValue] as any as Map[], + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: mapValue as Map, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asIterableOfT_valueOrSequence_ctor', + 'map-array-value'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = [ + new Map([[1, 'abc'], [2, 'def'], [3, 'ghi']]), + new Map([[4, 'jkl'], [5, 'mno'], [6, 'pqr']]), + new Map([[7, 'stu'], [8, 'vwx'], [9, 'yza']]), + ]; + // act + // assert + t.plan(1); + + testAsIterable>({ + ctor: Map, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asIterableOfT_valueOrSequence_ctor', + 'single-string-no-ctor'), + async t => { + await Promise.resolve(); + + // arrange + const stringValue = 'abc'; + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: undefined, + expected: [...stringValue], + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: stringValue, + }); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'asIterableOfT_valueOrSequence_ctor', + 'string-array-value-no-ctor'), + async t => { + await Promise.resolve(); + + // arrange + const expectedArray = ['abc', 'def', 'ghi']; + // act + // assert + t.plan(1); + + testAsIterable({ + ctor: undefined, + expected: expectedArray, + expectingMessage: 'Array should have three elements.', + target: Iterables, + test: t, + valueOrSequence: expectedArray, + }); +}); diff --git a/packages/@jali/util/test/iterables-concat.unit.test.ts b/packages/@jali/util/test/iterables-concat.unit.test.ts new file mode 100644 index 0000000..108c408 --- /dev/null +++ b/packages/@jali/util/test/iterables-concat.unit.test.ts @@ -0,0 +1,97 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// concatOfT_sequence_items + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'concatOfT_sequence_items', + 'two-sequences'), + async t => { + await Promise.resolve(); + + // arrange + const first = [1, 2, 3]; + const second = [4, 5, 6]; + const expected = first.concat(second); + const target = Iterables; + + // act + const actual = target.concat(first, second); + + // assert + t.plan(1); + + t.deepEqual([...actual], [...expected]); +}); + + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'concatOfT_sequence_items', + 'one-sequence'), + async t => { + await Promise.resolve(); + + // arrange + const first = [1, 2, 3]; + const second = [4, 5, 6]; + const third = [7, 8, 9]; + const expected = first.concat(second, third); + const target = Iterables; + + // act + const actual = target.concat(first, second, third); + + // assert + t.plan(1); + + t.deepEqual([...actual], [...expected]); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'concatOfT_sequence_items', + 'three-sequences'), + async t => { + await Promise.resolve(); + + // arrange + const first = [1, 2, 3]; + const second = [1, 2, 3]; + const expected = first.concat(second); + const target = Iterables; + + // act + const actual = target.concat(first, second); + + // assert + t.plan(1); + + t.deepEqual([...actual], [...expected]); +}); + + diff --git a/packages/@jali/util/test/iterables-every.unit.test.ts b/packages/@jali/util/test/iterables-every.unit.test.ts new file mode 100644 index 0000000..24cedf8 --- /dev/null +++ b/packages/@jali/util/test/iterables-every.unit.test.ts @@ -0,0 +1,168 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +import { toIterable } from '../testing/iterables-helpers'; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// everyOfT_sequence_test + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'everyOfT_sequence_test', + 'succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const test = (e: number, i: number, s: Iterable) => { + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return e % 2 === 0; + }; + const expected = true; + const target = Iterables; + + // act + const actual = target.every(sequence, test); + + // assert + t.plan(4); + + t.deepEqual([...actualElements], sequence); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.is(actual, expected, 'Every should be multiple of 2'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'everyOfT_sequence_test', + 'fails'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const test = (e: number, i: number, s: Iterable) => { + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return e % 3 !== 0; + }; + const expected = false; + const target = Iterables; + + // act + const actual = target.every(sequence, test); + + // assert + t.plan(4); + + t.deepEqual([...actualElements], sequence); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.is(actual, expected, 'Not every should NOT be multiple of 3'); +}); + + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'everyOfT_sequence_test', + 'succeeds-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const expectedElements = [...sequence]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const test = (e: number, i: number, s: Iterable) => { + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return e % 2 === 0; + }; + const expected = true; + const target = Iterables; + + // act + const actual = target.every(sequence, test); + + // assert + t.plan(4); + t.is(actualElements.length, 3); + t.deepEqual(actualElements, expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.is(actual, expected, 'Every should be multiple of 2'); +}); + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'everyOfT_sequence_test', + 'fails-not-array'), + async t => { + await Promise.resolve(); + + // arrange + // const sequence = toIterable([2, 4, 6]); + const sequence = Iterables.concat([2, 4, 6]); + const expectedElements = [...sequence]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const test = (e: number, i: number, s: Iterable) => { + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return e % 3 !== 0; + }; + const expected = false; + const target = Iterables; + + // act + const actual = target.every(sequence, test); + + // assert + t.plan(4); + t.deepEqual([...actualElements], expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.is(actual, expected, 'Not every should NOT be multiple of 3'); +}); diff --git a/packages/@jali/util/test/iterables-filter.unit.test.ts b/packages/@jali/util/test/iterables-filter.unit.test.ts new file mode 100644 index 0000000..5522402 --- /dev/null +++ b/packages/@jali/util/test/iterables-filter.unit.test.ts @@ -0,0 +1,44 @@ +import test from 'ava'; + +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// filterOfT_sequence_test + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'filterOfT_sequence_test', + 'two-matches'), + async t => { + await Promise.resolve(); + + // arrange + const first = {key: 'one', value: 1}; + const second = {key: 'two', value: 2}; + const third = {key: 'three', value: 3}; + const fourth = {key: 'four', value: 4}; + const expected = [second, third]; + const target = [first, second, third, fourth]; + + // act + const actual = Iterables.filter(target, v => v.key[0] === 't'); + + // assert + t.plan(1); + t.deepEqual([...actual], [...expected]); +}); diff --git a/packages/@jali/util/test/iterables-find.unit.test.ts b/packages/@jali/util/test/iterables-find.unit.test.ts new file mode 100644 index 0000000..b4a6f2b --- /dev/null +++ b/packages/@jali/util/test/iterables-find.unit.test.ts @@ -0,0 +1,163 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// findOfT_sequence_test + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'findOfT_sequence_test', + 'empty'), + async t => { + await Promise.resolve(); + + // arrange + const sequence: any[] = []; + const expected = undefined; + const target = Iterables; + + // act + const actual = target.find(sequence); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should not be found.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'findOfT_sequence_test', + 'non-empty'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, 2, 3]; + const expected = 1; + const target = Iterables; + + // act + const actual = target.find(sequence); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should find.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'findOfT_sequence_test', + 'no-match'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const test = (e: number) => e % 5 === 0; + const expected = undefined; + const target = Iterables; + + // act + const actual = target.find(sequence, test); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should not be found.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'findOfT_sequence_test', + 'matches'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const test = (e: number) => e % 3 === 0; + const expected = 6; + const target = Iterables; + + // act + const actual = target.find(sequence, test); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should find.'); +}); + + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'findOfT_sequence_test', + 'not-array-no-match'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = Iterables.concat([2, 4, 6]); + const test = (e: number) => e % 5 === 0; + const expected = undefined; + const target = Iterables; + + // act + const actual = target.find(sequence, test); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should not be found.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'findOfT_sequence_test', + 'not-array-matches'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = Iterables.concat([2, 4, 6]); + const test = (e: number) => e % 3 === 0; + const expected = 6; + const target = Iterables; + + // act + const actual = target.find(sequence, test); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should find.'); +}); diff --git a/packages/@jali/util/test/iterables-includes.unit.test.ts b/packages/@jali/util/test/iterables-includes.unit.test.ts new file mode 100644 index 0000000..a6b6c0f --- /dev/null +++ b/packages/@jali/util/test/iterables-includes.unit.test.ts @@ -0,0 +1,300 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + + +class FakeValue { + public constructor(public value: any) { + + } + + public valueOf(): Object { + return this.value; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// includesOfT_sequence_value_fromIndex + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'empty'), + async t => { + await Promise.resolve(); + + // arrange + const sequence: number[] = []; + const value = 1; + const fromIndex = undefined; + const expected = false; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should not include.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'includes'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, 2, 3]; + const value = 3; + const fromIndex = undefined; + const expected = true; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should include.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'not-includes'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, 2, 3]; + const value = 4; + const fromIndex = undefined; + const expected = false; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should include.'); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'has-value-with-default-parameters'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + const value = 1; + const target = [value]; + + // act + const actual = Iterables.includes(target, value); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'does-not-have-value-with-default-parameters'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + const value = 1; + const target = [2]; + + // act + const actual = Iterables.includes(target, value); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'includesOfT_sequence_value_fromIndex', + 'empty-from-zero'), + async t => { + await Promise.resolve(); + + // arrange + const sequence: number[] = []; + const value = 1; + const fromIndex = 0; + const expected = false; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should not include.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'includesOfT_sequence_value_fromIndex', + 'includes-from-index'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, 2, 3]; + const value = 3; + const fromIndex = 1; + const expected = true; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should include.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'includesOfT_sequence_value_fromIndex', + 'not-includes-from-index'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, 2, 3]; + const value = 4; + const fromIndex = 2; + const expected = false; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should include.'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Unit, + 'includesOfT_sequence_value_fromIndex', + 'not-includes-before-index'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, 2, 3]; + const value = 1; + const fromIndex = 2; + const expected = false; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should include.'); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'includes-NaN'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [1, NaN, 3]; + const value = NaN; + const fromIndex = undefined; + const expected = true; + const target = Iterables; + + // act + const actual = target.includes(sequence, value, fromIndex); + + // assert + t.plan(1); + + t.is(actual, expected, 'Should include.'); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'includesOfT_sequence_value_fromIndex', + 'does-not-have-value-not-loose'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + const value = new FakeValue(1); + const target = [1] as [Object]; + + // act + const actual = Iterables.includes(target, value); + + // assert + t.plan(1); + t.is(actual, expected); +}); diff --git a/packages/@jali/util/test/iterables-map.unit.test.ts b/packages/@jali/util/test/iterables-map.unit.test.ts new file mode 100644 index 0000000..b9c9acd --- /dev/null +++ b/packages/@jali/util/test/iterables-map.unit.test.ts @@ -0,0 +1,92 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +import { toIterable } from '../testing/iterables-helpers'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// mapOfT_sequence_converter + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'mapOfT_sequence_converter', + 'doubled'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const converter = (e: number, i: number, s: Iterable) => { + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return e * 2; + }; + const expected = [4, 8, 12]; + const target = Iterables; + + // act + const actual = target.map(sequence, converter); + const actualArray = Array.from(actual); + + // assert + t.plan(4); + + t.deepEqual([...actualElements], sequence); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.deepEqual(actualArray, expected, 'map should be doubled'); +}); + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'mapOfT_sequence_converter', + 'doubled-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const expectedElements = [...sequence]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const converter = (e: number, i: number, s: Iterable) => { + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return e * 2; + }; + const expected = [4, 8, 12]; + const target = Iterables; + + // act + const actual = target.map(sequence, converter); + const actualArray = Array.from(actual); + + // assert + t.plan(4); + t.deepEqual(actualElements, expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.deepEqual(actualArray, expected, 'map should be doubled'); +}); diff --git a/packages/@jali/util/test/iterables-reduce.unit.test.ts b/packages/@jali/util/test/iterables-reduce.unit.test.ts new file mode 100644 index 0000000..bbb713b --- /dev/null +++ b/packages/@jali/util/test/iterables-reduce.unit.test.ts @@ -0,0 +1,187 @@ +import test from 'ava'; +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +import { toIterable } from '../testing/iterables-helpers'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// reduceOfT_sequence_accumulator + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'reduceOfT_sequence_accumulator', + 'sum'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const expectedElements = [4, 6]; + const expectedPrevious = [2, 6]; + const expectedIndexes = [1, 2]; + const expectedSequences = [sequence, sequence]; + const actualPrevious: number[] = []; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const accumulator = (p: number, e: number, i: number, s: Iterable) => { + actualPrevious.push(p); + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return p + e; + }; + const expected = 12; + const target = Iterables; + + // act + const actual = target.reduce(sequence, accumulator); + + // assert + t.plan(5); + + t.deepEqual(actualPrevious, expectedPrevious); + t.deepEqual([...actualElements], expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.deepEqual(actual, expected, 'reduce should sum elements'); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'reduceOfT_sequence_accumulator', + 'sum-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const expectedElements = [4, 6]; + const expectedPrevious = [2, 6]; + const expectedIndexes = [1, 2]; + const expectedSequences = [sequence, sequence]; + const actualPrevious: number[] = []; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const accumulator = (p: number, e: number, i: number, s: Iterable) => { + actualPrevious.push(p); + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return p + e; + }; + const expected = 12; + const target = Iterables; + + // act + const actual = target.reduce(sequence, accumulator); + + // assert + t.plan(5); + + t.deepEqual(actualPrevious, expectedPrevious); + t.deepEqual([...actualElements], expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.deepEqual(actual, expected, 'reduce should sum elements'); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'reduceOfT_sequence_accumulator', + 'specified-initial-sum'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const expectedElements = sequence; + const expectedPrevious = [1, 3, 7]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualPrevious: number[] = []; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const accumulator = (p: number, e: number, i: number, s: Iterable) => { + actualPrevious.push(p); + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return p + e; + }; + const initialValue = 1; + const expected = 12 + initialValue; + const target = Iterables; + + // act + const actual = target.reduce(sequence, accumulator, initialValue); + + // assert + t.plan(5); + + t.deepEqual(actualPrevious, expectedPrevious); + t.deepEqual([...actualElements], expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.deepEqual(actual, expected, 'reduce should sum elements'); +}); + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'reduceOfT_sequence_accumulator', + 'specified-initial-sum-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const expectedElements = [...sequence]; + const expectedPrevious = [1, 3, 7]; + const expectedIndexes = [0, 1, 2]; + const expectedSequences = [sequence, sequence, sequence]; + const actualPrevious: number[] = []; + const actualElements: number[] = []; + const actualIndexes: number[] = []; + const actualSequences: Iterable[] = []; + const accumulator = (p: number, e: number, i: number, s: Iterable) => { + actualPrevious.push(p); + actualElements.push(e); + actualIndexes.push(i); + actualSequences.push(s); + return p + e; + }; + const initialValue = 1; + const expected = 12 + initialValue; + const target = Iterables; + + // act + const actual = target.reduce(sequence, accumulator, initialValue); + + // assert + t.plan(5); + + t.deepEqual(actualPrevious, expectedPrevious); + t.deepEqual([...actualElements], expectedElements); + t.deepEqual([...actualIndexes], [...expectedIndexes]); + t.deepEqual([...actualSequences], [...expectedSequences]); + t.deepEqual(actual, expected, 'reduce should sum elements'); +}); diff --git a/packages/@jali/util/test/iterables-slice.unit.test.ts b/packages/@jali/util/test/iterables-slice.unit.test.ts new file mode 100644 index 0000000..1f649bd --- /dev/null +++ b/packages/@jali/util/test/iterables-slice.unit.test.ts @@ -0,0 +1,225 @@ +import test from 'ava'; + +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + +import { toIterable } from '../testing/iterables-helpers'; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// sliceOfT_sequence_begin_end + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'no-end'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const begin = 1; + const end = undefined; + const expectedSequence = [4, 6]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'with-end'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const begin = 0; + const end = 2; + const expectedSequence = [2, 4]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'no-end-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const begin = 1; + const end = undefined; + const expectedSequence = [4, 6]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'with-end-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const begin = 0; + const end = 2; + const expectedSequence = [2, 4]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + + +////////////// +// Unit tests + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'no-end-reverse'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const begin = -1; + const end = undefined; + const expectedSequence = [6]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'with-end-reverse'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const begin = -2; + const end = 2; + const expectedSequence = [4]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'sliceOfT_sequence_begin_end', + 'with-negative-end'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = [2, 4, 6]; + const begin = 0; + const end = -1; + const expectedSequence = [2, 4]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + + +/** ***********************************************************************************************/ +test.failing(title(TestType.Smoke, 'sliceOfT_sequence_begin_end', + 'no-end-reverse-not-array'), + async t => { + await Promise.resolve(); + + // arrange + const sequence = toIterable([2, 4, 6]); + const begin = -1; + const end = undefined; + const expectedSequence = [6]; + const target = Iterables; + + // act + const actual = target.slice(sequence, begin, end); + const actualSequence = Array.from(actual); + + // assert + t.plan(1); + t.deepEqual(actualSequence, expectedSequence); +}); + diff --git a/packages/@jali/util/test/iterables-some.unit.test.ts b/packages/@jali/util/test/iterables-some.unit.test.ts new file mode 100644 index 0000000..39ef213 --- /dev/null +++ b/packages/@jali/util/test/iterables-some.unit.test.ts @@ -0,0 +1,109 @@ +import test from 'ava'; + +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// someOfT_sequence + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'someOfT_sequence', + 'empty'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + const target: any[] = []; + + // act + const actual = Iterables.some(target); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'someOfT_sequence', + 'not-empty'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + const target = [1]; + + // act + const actual = Iterables.some(target); + + // assert + t.plan(1); + t.is(actual, expected); +}); + + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'someOfT_sequence_test', + 'test-succeeds'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + const test = (element: number) => element === 1; + const target = [0, 1]; + + // act + const actual = Iterables.some(target, test); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'someOfT_sequence_test', + 'test-fails'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + const test = (element: number) => element === 1; + const target = [0, 2]; + + // act + const actual = Iterables.some(target, test); + + // assert + t.plan(1); + t.is(actual, expected); +}); + + diff --git a/packages/@jali/util/test/iterables-to-map.unit.test.ts b/packages/@jali/util/test/iterables-to-map.unit.test.ts new file mode 100644 index 0000000..023e330 --- /dev/null +++ b/packages/@jali/util/test/iterables-to-map.unit.test.ts @@ -0,0 +1,45 @@ +import test from 'ava'; + +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as Iterables from '../src/iterables'; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'Iterables'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// toMapOfTKeyTValue_sequence_keySelector + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'toMapOfTKeyTValue_sequence_keySelector', + 'string-key'), + async t => { + await Promise.resolve(); + + // arrange + const first = {key: 'one', value: 1}; + const second = {key: 'two', value: 2}; + const target = [first, second]; + const expected = new Map([[first.key, first], [second.key, second]]); + + // act + const actual = Iterables.toMap(target, v => v.key); + + // assert + t.plan(1); + // spread used for bug in babel Map constructor. + t.deepEqual([...actual], [...expected]); +}); diff --git a/packages/@jali/util/test/type-guards.unit.test.ts b/packages/@jali/util/test/type-guards.unit.test.ts new file mode 100644 index 0000000..220a000 --- /dev/null +++ b/packages/@jali/util/test/type-guards.unit.test.ts @@ -0,0 +1,358 @@ +import test from 'ava'; + +import {makeTitleFunc, TestType, ProductEpic, RepoPackage, } from '../testing'; + +import * as TypeGuards from '../src/type-guards'; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +let title = makeTitleFunc( + ProductEpic.Util, + RepoPackage.Util, + 'TypeGuards'); +//////////////////////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// isError + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'isError', + 'is-error'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + const value = new Error(); + const target = TypeGuards; + + // act + const actual = target.isError(value); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'isError', + 'is-not-error'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + const value = 'error'; + const target = TypeGuards; + + // act + const actual = target.isError(value); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// isIterableOfT_value + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'isIterableOfT_value', + 'is-iterable'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + const value: any[] = []; + const target = TypeGuards; + + // act + const actual = target.isIterable(value); + + // assert + t.plan(1); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'isIterableOfT_value', + 'is-not-iterable'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + const value = 1; + const target = TypeGuards; + + // act + const actual = target.isIterable(value); + + // assert + t.plan(1); + t.is(actual, expected); +}); + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// makeIsIterableOfT_elementTypeGuard_deep + +////////////// +// Smoke tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-iterable_empty_default_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = undefined; + const value: any[] = []; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-iterable_nonempty_default_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = undefined; + const value = [1]; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-not-iterable_default_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = undefined; + const value = 1; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-not-element_default_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = undefined; + const value = ['one']; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + + +////////////// +// Unit tests + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-iterable_empty_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = true; + const value: any[] = []; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-iterable_nonempty_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = true; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = true; + const value = [1, 2]; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-not-iterable_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = true; + const value = 1; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + +/** ***********************************************************************************************/ +test( + title( + TestType.Smoke, + 'makeIsIterableOfT_elementTypeGuard_deep', + 'is-not-element_nonempty_deep'), + async t => { + await Promise.resolve(); + + // arrange + const expected = false; + + const test: (value: any) => value is number = + ((value: any) => typeof value === 'number') as (value: any) => value is number; + + const deep = true; + const value = [1, 'two']; + const target = TypeGuards; + + // act + const actualFunction = target.makeIsIterable(test, deep); + const actual = actualFunction(value); + + // assert + t.plan(2); + t.is(typeof actualFunction, 'function'); + t.is(actual, expected); +}); + diff --git a/packages/@jali/util/testing/argument-error-helpers.ts b/packages/@jali/util/testing/argument-error-helpers.ts new file mode 100644 index 0000000..e9b0641 --- /dev/null +++ b/packages/@jali/util/testing/argument-error-helpers.ts @@ -0,0 +1,70 @@ +import { ContextualTestContext } from 'ava'; + +import ArgumentTypeError from '../src/argument-type-error'; + +export interface TestContext { + test: ContextualTestContext; + classConstructor: new (name?: string, message?: string) => TError; + error?: TError; + parameterName?: string; + errorMessage?: string; + defaultMessage?: string; +} + +export function testArgumentError(context: TestContext) { + // arrange + const t = context.test; + const name = context.parameterName; + const message = context.errorMessage || context.defaultMessage; + + const expectedMessage = (context.error) + ? context.errorMessage + : `Error in argument${name ? ` '${name}'` : ''}${(message) ? `: ${message}` : ''}`; + + const target = context.classConstructor; + + // act + const result = context.error || new target(context.parameterName, context.errorMessage); + + // assert + t.true(result instanceof context.classConstructor); + + const actualMessage = result.message; + t.is(actualMessage, expectedMessage); +} + +export interface ArgumentTypeErrorTestContext { + test: ContextualTestContext; + classConstructor?: Function; + error?: Error; + type: string; + parameterName?: string; + errorMessage?: string; +} + +export function testArgumentTypeError(context: ArgumentTypeErrorTestContext) { + // arrange + const t = context.test; + const errorConstructor = context.classConstructor || ArgumentTypeError; + const type = context.type; + const name = context.parameterName; + const message = context.errorMessage || `Argument must have type '${type}'. Yours is not`; + + const expectedMessage = (context.error) + ? context.errorMessage + : `Error in argument${name ? ` '${name}'` : ''}${(message) ? `: ${message}` : ''}`; + + const target = ArgumentTypeError; + + // act + const result = context.error || new target(type, name, message); + + // assert + t.true( + result instanceof errorConstructor, + `Object of type '${result.constructor.name}' does not have error type ` + + `'${errorConstructor.name}'`); + + const actualMessage = result.message; + t.is(actualMessage, expectedMessage); +} diff --git a/packages/@jali/util/testing/helpers.ts b/packages/@jali/util/testing/helpers.ts new file mode 100644 index 0000000..35758a9 --- /dev/null +++ b/packages/@jali/util/testing/helpers.ts @@ -0,0 +1,39 @@ +import { default as ProductEpic } from './product-epic'; +import { default as RepoPackage } from './repo-package'; +import { default as TestDescription } from './test-description'; +import { default as TestDisposition} from './test-disposition'; +import { default as TestType } from './test-type'; + +export function makeTitle(description: TestDescription): string { + const type: string = TestType[description.type]; + const polarity = TestDisposition[description.disposition]; + const epic: string = ProductEpic[description.epic]; + const pkg = RepoPackage[description.package]; + const func = description.functionName; + const descr = description.description; + + const scope = `typeβ€Ώ${type}οΌΏdispositionβ€Ώ${polarity}οΌΏepicβ€Ώ${epic}οΌΏpackageβ€Ώ${pkg}`; + + return `${scope}οΌΏfunctionβ€Ώ${func}οΌΏ${descr}`; +} + +export function makeTitleFunc(epic: ProductEpic, pkg: RepoPackage, targetName: string): + ( + type: TestType, + functionName: string, + description: string, + disposition?: TestDisposition) => string { + return ( + type: TestType, + functionName: string, + description: string, + disposition: TestDisposition = TestDisposition.Positive + ) => makeTitle({ + description: description, + disposition: disposition, + epic: epic, + functionName: `${targetName ? `${targetName}﹍` : ''}${functionName}`, + package: pkg, + type: type, + } as TestDescription); +} diff --git a/packages/@jali/util/testing/index.ts b/packages/@jali/util/testing/index.ts new file mode 100644 index 0000000..51d8972 --- /dev/null +++ b/packages/@jali/util/testing/index.ts @@ -0,0 +1,7 @@ +export { default as ProductEpic } from './product-epic'; +export { default as RepoPackage } from './repo-package'; +export { default as TestDescription } from './test-description'; +export { default as TestDisposition} from './test-disposition'; +export { default as TestType } from './test-type'; + +export * from './helpers'; diff --git a/packages/@jali/util/testing/iterables-helpers.ts b/packages/@jali/util/testing/iterables-helpers.ts new file mode 100644 index 0000000..523565b --- /dev/null +++ b/packages/@jali/util/testing/iterables-helpers.ts @@ -0,0 +1,67 @@ +import { ContextualTestContext } from 'ava'; + +import * as Iterables from '../src/iterables'; + + +export function *toIterable(arr: number[]) { + yield *arr; +} + +export type asArrayOfT_valueOrSequence_type = T | Iterable | undefined; +export type asArrayOfT_ctor_type = (new(...args: any[]) => any) | undefined; +export type asArrayOfT_return_type = T[]; + +// tslint:disable-next-line:class-name +export interface AsArrayOfT_TestContext { + test: ContextualTestContext; + valueOrSequence: asArrayOfT_valueOrSequence_type; + ctor: asArrayOfT_ctor_type; + target: typeof Iterables; + expected: asArrayOfT_return_type; + expectingMessage: string; +} + +export function testAsArray(context: AsArrayOfT_TestContext) { + // arrange + const t = context.test; + const valueOrSequence = context.valueOrSequence; + const ctor = context.ctor; + const target = context.target; + const expected = context.expected; + + // act + const actual = target.asArray(valueOrSequence, ctor); + + // assert + t.deepEqual(actual, expected, context.expectingMessage); +} + + +export type asIterableOfT_valueOrSequence_type = T | Iterable | undefined; +export type asIterableOfT_ctor_type = (new(...args: any[]) => any) | undefined; +export type asIterableOfT_return_type = Iterable; + +// tslint:disable-next-line:class-name +export interface AsIterableOfT_TestContext { + test: ContextualTestContext; + valueOrSequence: asIterableOfT_valueOrSequence_type; + ctor: asIterableOfT_ctor_type; + target: typeof Iterables; + expected: asIterableOfT_return_type; + expectingMessage: string; +} + +export function testAsIterable(context: AsIterableOfT_TestContext) { + // arrange + const t = context.test; + const valueOrSequence = context.valueOrSequence; + const ctor = context.ctor; + const target = context.target; + const expected = context.expected; + + // act + const actual = target.asIterable(valueOrSequence, ctor); + + // assert + t.deepEqual(actual, expected, context.expectingMessage); +} diff --git a/packages/@jali/util/testing/product-epic.ts b/packages/@jali/util/testing/product-epic.ts new file mode 100644 index 0000000..5f520b0 --- /dev/null +++ b/packages/@jali/util/testing/product-epic.ts @@ -0,0 +1,8 @@ +enum ProductEpic { + Core, + DevEnv, + Note, + Util, +} + +export default ProductEpic; diff --git a/packages/@jali/util/testing/repo-package.ts b/packages/@jali/util/testing/repo-package.ts new file mode 100644 index 0000000..44e0582 --- /dev/null +++ b/packages/@jali/util/testing/repo-package.ts @@ -0,0 +1,8 @@ +enum RepoPackage { + None, + Core, + Note, + Util, +} + +export default RepoPackage; diff --git a/packages/@jali/util/testing/test-description.ts b/packages/@jali/util/testing/test-description.ts new file mode 100644 index 0000000..cbde0bc --- /dev/null +++ b/packages/@jali/util/testing/test-description.ts @@ -0,0 +1,15 @@ +import { default as ProductEpic } from './product-epic'; +import { default as RepoPackage } from './repo-package'; +import { default as TestDisposition} from './test-disposition'; +import { default as TestType } from './test-type'; + +interface TestDescription { + readonly type: TestType; + readonly disposition: TestDisposition; + readonly epic: ProductEpic; + readonly package: RepoPackage; + readonly functionName: string; + readonly description: string; +} + +export default TestDescription; diff --git a/packages/@jali/util/testing/test-disposition.ts b/packages/@jali/util/testing/test-disposition.ts new file mode 100644 index 0000000..dab7ad5 --- /dev/null +++ b/packages/@jali/util/testing/test-disposition.ts @@ -0,0 +1,6 @@ +enum TestDisposition { + Positive, + Negative, +} + +export default TestDisposition; diff --git a/packages/@jali/util/testing/test-type.ts b/packages/@jali/util/testing/test-type.ts new file mode 100644 index 0000000..7d9eb88 --- /dev/null +++ b/packages/@jali/util/testing/test-type.ts @@ -0,0 +1,7 @@ +enum TestType { + Smoke, + Unit, + Integration, +} + +export default TestType; diff --git a/packages/@jali/util/tsconfig-build.json b/packages/@jali/util/tsconfig-build.json new file mode 100644 index 0000000..bcc1e4f --- /dev/null +++ b/packages/@jali/util/tsconfig-build.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": ["es2017"], + "module": "es2015", + "moduleResolution": "node", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "../../../dist/packages-dist/@jali/util/", + "paths": { + }, + "rootDir": ".", + "strictNullChecks": true, + "stripInternal": true, + "target": "es6", + "typeRoots": ["../../../node_modules/@types/node"], + "types": ["node"] + }, + "files": [ + "index.ts", + "iterables/index.ts", + "type-guards/index.ts" + ], + "include": [ + "test/*.ts", + "testing/*.ts" + ] +} + + diff --git a/packages/@jali/util/type-guards/index.ts b/packages/@jali/util/type-guards/index.ts new file mode 100644 index 0000000..1b38fbf --- /dev/null +++ b/packages/@jali/util/type-guards/index.ts @@ -0,0 +1 @@ +export * from '../src/type-guards'; diff --git a/packages/@jali/util/webpackfile.js b/packages/@jali/util/webpackfile.js new file mode 100644 index 0000000..50ffed1 --- /dev/null +++ b/packages/@jali/util/webpackfile.js @@ -0,0 +1,107 @@ +/* eslint-disable no-unused-vars */ +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const path = require('path'); +const webpack = require('webpack'); + +const scopeName = 'jali'; +const npmScope = '@jali'; +const packageName = 'util'; +const packageDirName = 'packages'; +const entryPointName = 'index.js'; +const distDirName = 'dist'; +const packagesDistDirName = 'packages-dist'; +const bundlesDistDirName = 'bundles'; + +const packagesDir = `./${packageDirName}`; +const scopeDir = `${packagesDir}/${npmScope}`; +const packageDir = `${scopeDir}/${packageName}`; + +const projectDir = `.`; +const distDir = `./${distDirName}`; +const distPackagesDir = `${distDir}/${packagesDistDirName}`; +const distScopeDir = `${distPackagesDir}/${npmScope}`; +const distPackageDir = `${distScopeDir}/${packageName}`; +const distPackageEntryPoint = `${distPackageDir}/${entryPointName}`; +const distBundleDir = `${distPackageDir}/${bundlesDistDirName}`; + +// Export webpack configuration function, passing in command line options. +module.exports = function(options) { + const config = { + devtool: 'source-map', + entry: { + util: [distPackageEntryPoint], + }, + // externals: ['babel-polyfill'], + module: { + loaders: [ + { + exclude: /node_modules/, + loader: 'babel-loader', + query: { + cacheDirectory: true, + plugins: [ + 'syntax-trailing-function-commas', + 'transform-async-to-generator', + 'transform-class-properties', + 'transform-decorators-legacy', + 'transform-es2015-function-name', + 'transform-es2015-modules-commonjs', + 'transform-function-sent', + 'transform-exponentiation-operator', + [ + 'transform-runtime', + { + polyfill: false, + regenerator: false, + } + ] + ], + presets: [] + }, + test: /\.js$/, + } + ] + }, + output: { + filename: '[name].umd.js', + path: distBundleDir, + }, + plugins: [ + new CopyWebpackPlugin([ + { + context: packageDir, + from: '.npmignore', + to: '..', + }, + { + context: packageDir, + from: 'package.json', + to: '..', + }, + { + context: packageDir, + from: 'README.md', + to: '..', + }, + { + context: projectDir, + from: 'LICENSE', + to: '..' + }, + ]), + ], + resolve: { + modules: [ + './node_modules' + ] + }, + stats: { + colors: true, + errorDetails: true, + modules: true, + reasons: true, + }, + }; + + return config; +}; diff --git a/packages/tsconfig.json b/packages/tsconfig.json new file mode 100644 index 0000000..91662b4 --- /dev/null +++ b/packages/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2017"], + "module": "es2015", + "moduleResolution": "node", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "../dist/all/", + "paths": { + "@jali/*": ["./@jali/*"] + }, + "rootDir": ".", + "strictNullChecks": true, + "stripInternal": true, + "target": "es6", + "typeRoots": ["../../node_modules/@types/node"], + "types": ["node"] + }, + "include": [ + "**/*.ts" + ] +} + + diff --git a/site-cookbooks/main/Berksfile b/site-cookbooks/main/Berksfile index 967b9a7..34fea21 100644 --- a/site-cookbooks/main/Berksfile +++ b/site-cookbooks/main/Berksfile @@ -1,3 +1,3 @@ -source "https://supermarket.chef.io" +source 'https://supermarket.chef.io' metadata diff --git a/site-cookbooks/main/CHANGELOG.md b/site-cookbooks/main/CHANGELOG.md index c759548..7c6fab8 100644 --- a/site-cookbooks/main/CHANGELOG.md +++ b/site-cookbooks/main/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.1.1 + +updated depends versions + # 0.1.0 Initial release of main diff --git a/site-cookbooks/main/metadata.rb b/site-cookbooks/main/metadata.rb index 8439c90..044f071 100644 --- a/site-cookbooks/main/metadata.rb +++ b/site-cookbooks/main/metadata.rb @@ -4,7 +4,7 @@ license 'All rights reserved' description 'Installs/Configures main' long_description 'Installs/Configures main' -version '0.1.0' +version '0.1.1' -depends 'apt', '~> 3.0.0' -depends 'docker', '~> 2.6.8' +depends 'apt', '~> 5.0.1' +depends 'docker', '~> 2.13.9' diff --git a/site-cookbooks/main/recipes/default.rb b/site-cookbooks/main/recipes/default.rb index bf773a1..c0548b0 100644 --- a/site-cookbooks/main/recipes/default.rb +++ b/site-cookbooks/main/recipes/default.rb @@ -1,17 +1,14 @@ # Cookbook Name:: main # Recipe:: default - # Run apt-update include_recipe 'apt::default' - # Install gksudo apt_package 'gksu' do action :install end - # Install Docker docker_service 'default' do action [:create, :start] @@ -56,13 +53,12 @@ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - EOH end - + package 'nodejs' do action :install end end - # Install Sphinx documentation generator # http://stackoverflow.com/a/23922391 apt_package 'python3-sphinx' do @@ -70,13 +66,23 @@ end # Install VSCode +# Update instructions: +# Download: +# - Go to https://code.visualstudio.com/Docs/?dv=linux64_deb +# - Node downloaded file. +# - Copy link address of "direct download link" and paste as value of "source" +# below. +# - Download http://www.labtestproject.com/files/win/sha256sum/sha256sum.exe +# - Follow instructions, +# http://www.labtestproject.com/using_windows/step_by_step_using_sha256sum_on_windows_xp.html +# to obtain checksum of downloaded file and paste below. if ::Dir.exist?('/usr/share/code/') Chef::Log.info('(up to date)') else remote_file "#{Chef::Config[:file_cache_path]}/visual-studio-code.deb" do + # or http://code.visualstudio.com/docs/?dv=linux64_deb ? source 'https://go.microsoft.com/fwlink/?LinkID=760868' mode 0644 - checksum 'f7457df3c94b459b055b31ed1e3f2d22e1993e18c1b6209fd5aa98651c125a43' end dpkg_package 'visual-studio-code' do @@ -94,10 +100,6 @@ # Set shell functions and aliases file '/home/vagrant/.bash_aliases' do - # Verfiy that --disable-gpu is needed on distros other than Ubuntu 14.04 - content <<-EOH - code () { command -p code "$@" --disable-gpu ; } - EOH end # Set up project diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b02c4ba --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "inlineSources": true, + "lib": ["es2017"], + "module": "es2015", + "moduleResolution": "node", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "./dist/all/", + "paths": { + "@jali/*": ["./packages/@jali/*"] + }, + "rootDir": ".", + "strictNullChecks": true, + "stripInternal": true, + "target": "es6", + "typeRoots": ["./node_modules/@types/node"], + "types": ["node"] + }, + "files": [ + "./typings/index.d.ts" + ] +} + + diff --git a/tslint.json b/tslint.json index 7e94111..b1dfb11 100644 --- a/tslint.json +++ b/tslint.json @@ -1,27 +1,69 @@ { "rulesDirectory": ["node_modules/codelyzer"], "rules": { - "max-line-length": [true, 100], - "no-inferrable-types": true, "class-name": true, "comment-format": [ true, "check-space" ], + "curly": true, + "eofline": true, + "forin": true, "indent": [ true, "spaces" ], - "eofline": true, - "no-duplicate-variable": true, - "no-eval": true, + "label-position": true, + "max-line-length": [ + true, + 100 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "public-static-field", + "public-instance-field", + "constructor", + "public-static-method", + "public-instance-method", + "protected-static-field", + "protected-instance-field", + "protected-static-method", + "protected-instance-method", + "private-static-field", + "private-instance-field", + "private-static-method", + "private-instance-method" + ] + } + ], "no-arg": true, "no-internal-module": true, - "no-trailing-whitespace": true, "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": true, "no-shadowed-variable": true, + "no-string-literal": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, "no-unused-expression": true, - "no-unused-variable": true, + "no-use-before-declare": false, + "no-var-keyword": true, + "object-literal-sort-keys": true, "one-line": [ true, "check-catch", @@ -31,10 +73,17 @@ ], "quotemark": [ true, - "single", - "avoid-escape" + "avoid-escape", + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" ], - "semicolon": [true, "always"], "typedef-whitespace": [ true, { @@ -45,7 +94,6 @@ "variable-declaration": "nospace" } ], - "curly": true, "variable-name": [ true, "ban-keywords", @@ -59,14 +107,20 @@ "check-operator", "check-separator", "check-type" - ], + ]/*, + "component-class-suffix": true, "component-selector-name": [true, "kebab-case"], "component-selector-type": [true, "element"], - "host-parameter-decorator": true, - "input-parameter-decorator": true, - "output-parameter-decorator": true, - "attribute-parameter-decorator": true, - "input-property-directive": true, - "output-property-directive": true + "directive-class-suffix": true, + "directive-selector-name": [true, "camelCase"], + "directive-selector-type": [true, "attribute"], + "no-attribute-parameter-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-host-property-decorator": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "use-output-property-decorator": true*/ } } diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..795e5cc --- /dev/null +++ b/typedoc.json @@ -0,0 +1,21 @@ +{ + "emitDecoratorMetadata": "true", + "exclude": [ + "./dist/", + "./**/*.test.ts", + "./**/testing/*.ts" + ], + "experimentalDecorators": "true", + "ignoreCompilerErrors": "true", + "mode": "modules", + "module": "commonjs", + "moduleResolution": "node", + "name": "Jali Specification-driven Serverless Microservice Framework", + "out": "./dist/all/doc", + "preserveConstEnums": "true", + "stripInternal": "true", + "suppressExcessPropertyErrors": "true", + "suppressImplicitAnyIndexErrors": "true", + "target": "ES6", + "theme": "default" +} \ No newline at end of file diff --git a/typings/declarations/declarations.d.ts b/typings/declarations/declarations.d.ts new file mode 100644 index 0000000..5aed5ba --- /dev/null +++ b/typings/declarations/declarations.d.ts @@ -0,0 +1,10 @@ +// https://github.com/Microsoft/TypeScript/issues/6615 +declare module 'glob-fs' { + var _temp: any; + export = _temp; +} + +declare module 'app-root-path' { + var _temp: any; + export = _temp; +} diff --git a/typings/index.d.ts b/typings/index.d.ts new file mode 100644 index 0000000..6b62252 --- /dev/null +++ b/typings/index.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/webpackfile.js b/webpackfile.js index d4e1bce..1af647b 100644 --- a/webpackfile.js +++ b/webpackfile.js @@ -1,18 +1,18 @@ /** * Provides configuration for the [`webpack`](https://webpack.github.io/) module bundler. - * Copied from [`Angular 2 Starter`](https://angularclass.github.io/angular2-webpack-starter/) + * Copied from [`Angular 2 Starter`](https://angularclass.github.io/angular2-webpack-starter/) * commit [`2b29948`](https://github.com/AngularClass/angular2-webpack-starter/blob/918a2c912533b98da9bdc17c906ff2a96189aaab/webpack.config.js). - * Incorporates wepack 2 features as specified in [What's new in webpack 2](https://gist.github.com/sokra/27b24881210b56bbaff7) + * Incorporates webpack 2 features as specified in [What's new in webpack 2](https://gist.github.com/sokra/27b24881210b56bbaff7) * @author Latticework */ -exports default (options) => { - return { - - } -} +exports (options) => { + return new Promise((resolve, reject) => { -// Look in ./config folder for webpack.dev.js + switch (options.environment) { + case "production": + + } switch (process.env.NODE_ENV) { case 'prod': case 'production': @@ -26,4 +26,8 @@ switch (process.env.NODE_ENV) { case 'development': default: module.exports = require('./config/webpack.dev'); -} \ No newline at end of file +} + + + } +}