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 +} + + + } +}